Anpassa autentisering med Microsoft. Identity.Web

Microsoft. Identity.Web tillhandahåller säkra standardinställningar för autentisering och auktorisering i ASP.NET Core program som integreras med Microsoft Entra ID. Du kan anpassa många aspekter av autentiseringsbeteendet samtidigt som du bevarar bibliotekets inbyggda säkerhetsfunktioner.

Identifiera anpassningsbara områden

Område Anpassningsalternativ
Konfiguration Alla MicrosoftIdentityOptions, OpenIdConnectOptions, JwtBearerOptions egenskaper
Events OpenID Connect-händelser (OnTokenValidated, OnRedirectToIdentityProviderosv.)
Tokenförvärv Korrelations-ID, extra frågeparametrar
Claims Lägga till anpassade anspråk i ClaimsPrincipal
Användargränssnitt Utloggningssidor, omdirigeringsbeteende
Logga in Inloggningstips, domäntips

Välj en anpassningsmetod

I följande tabell sammanfattas de områden som du kan anpassa och vad varje område stöder.

Använd någon av två metoder för att anpassa alternativ:

  1. Configure<TOptions> – Konfigurerar alternativ innan de används
  2. PostConfigure<TOptions> – Konfigurerar alternativ efter alla Configure anrop

Körningsordning:

Configure → Configure → ... → PostConfigure → PostConfigure → ... → Options used

Konfigurera autentiseringsalternativ

Det här avsnittet visar hur du konfigurerar de olika autentiseringsalternativklasserna som Microsoft. Identity.Web använder.

Förstå konfigurationsmappning

Avsnittet "AzureAd" i appsettings.json mappar till flera klasser:

Du kan använda valfri egenskap från dessa klasser i konfigurationen.

Mönster 1: Konfigurera MicrosoftIdentityOptions

Följande kod anpassar MicrosoftIdentityOptions för att aktivera PII-loggning, ange klientfunktioner och justera tokenverifieringsparametrar:

using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));

// Customize Microsoft Identity options
builder.Services.Configure<MicrosoftIdentityOptions>(options =>
{
    // Enable PII logging (development only!)
    options.EnablePiiLogging = true;

    // Custom client capabilities
    options.ClientCapabilities = new[] { "CP1", "CP2" };

    // Override token validation parameters
    options.TokenValidationParameters.ValidateLifetime = true;
    options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(5);
});

var app = builder.Build();

Mönster 2: Konfigurera OpenIdConnectOptions (webbapplikationer)

Följande kod anpassar OpenIdConnectOptions för en webbapp för att ange svarstyp, lägga till omfång och konfigurera inställningar för cookie- och tokenverifiering:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));

// Customize OpenIdConnect options
builder.Services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme,
    options =>
{
    // Override response type
    options.ResponseType = "code id_token";

    // Add extra scopes
    options.Scope.Add("offline_access");
    options.Scope.Add("profile");

    // Customize token validation
    options.TokenValidationParameters.NameClaimType = "preferred_username";
    options.TokenValidationParameters.RoleClaimType = "roles";

    // Set redirect URI
    options.CallbackPath = "/signin-oidc";

    // Configure cookie options
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.Lax;
});

Mönster 3: Konfigurera JwtBearerOptions (webb-API)

Följande kod anpassar JwtBearerOptions för ett webb-API för att ange giltiga målgrupper, anspråksmappningar och validering av tokenlivslängd:

using Microsoft.AspNetCore.Authentication.JwtBearer;

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

// Customize JWT Bearer options
builder.Services.Configure<JwtBearerOptions>(
    JwtBearerDefaults.AuthenticationScheme,
    options =>
{
    // Customize audience validation
    options.TokenValidationParameters.ValidAudiences = new[]
    {
        "api://your-api-client-id",
        "https://your-api.com"
    };

    // Set custom claim mappings
    options.TokenValidationParameters.NameClaimType = "name";
    options.TokenValidationParameters.RoleClaimType = "roles";

    // Customize token validation
    options.TokenValidationParameters.ValidateLifetime = true;
    options.TokenValidationParameters.ClockSkew = TimeSpan.Zero; // No tolerance
});

Följande kod konfigurerar alternativ för cookieprincip och cookieautentisering för din app, inklusive säkerhetsinställningar och förfallobeteende:

using Microsoft.AspNetCore.Authentication.Cookies;

// Configure cookie policy
builder.Services.Configure<CookiePolicyOptions>(options =>
{
    options.MinimumSameSitePolicy = SameSiteMode.Lax;
    options.Secure = CookieSecurePolicy.Always;
    options.HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always;
});

// Configure cookie authentication options
builder.Services.Configure<CookieAuthenticationOptions>(
    CookieAuthenticationDefaults.AuthenticationScheme,
    options =>
{
    options.Cookie.Name = "MyApp.Auth";
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.Lax;
    options.ExpireTimeSpan = TimeSpan.FromHours(1);
    options.SlidingExpiration = true;
});

Anpassa händelsehanterare

OpenID Connect- och JWT Bearer-autentisering exponerar händelser som du kan ansluta till. Microsoft. Identity.Web konfigurerar sina egna händelsehanterare, så du måste länka dina anpassade hanterare med de befintliga för att bevara inbyggda funktioner.

Bevara befintliga hanterare

När du lägger till anpassade händelsehanterare sparar och anropar du alltid den befintliga hanteraren först. I följande exempel visas fel och korrekta metoder.

Följande kod felaktigt skriver över Microsoft.Identity.Web-hanterare:

services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
    options.Events.OnTokenValidated = async context =>
    {
        // Your code - but you LOST the built-in validation!
        await Task.CompletedTask;
    };
});

Följande kod kedjas korrekt med den befintliga hanteraren:

services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
    var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;

    options.Events.OnTokenValidated = async context =>
    {
        // Call Microsoft.Identity.Web's handler FIRST
        await existingOnTokenValidatedHandler(context);

        // Then your custom code
        // (executes AFTER built-in security checks)
        var identity = context.Principal.Identity as ClaimsIdentity;
        identity?.AddClaim(new Claim("custom_claim", "custom_value"));
    };
});

Tillämpa vanliga händelsescenarier

Lägga till anpassade anspråk efter tokenverifiering

Följande kod lägger till anpassade claims till ClaimsPrincipal efter tokenvalidering i ett webb-API. Den söker upp användarens avdelning från en databas och tilldelar en programspecifik roll baserat på e-postdomänen:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Security.Claims;

builder.Services.Configure<JwtBearerOptions>(
    JwtBearerDefaults.AuthenticationScheme,
    options =>
{
    var existingHandler = options.Events.OnTokenValidated;

    options.Events.OnTokenValidated = async context =>
    {
        // Preserve built-in validation
        await existingHandler(context);

        // Add custom claims
        var identity = context.Principal.Identity as ClaimsIdentity;

        // Example: Add department claim from database
        var userObjectId = context.Principal.FindFirst("oid")?.Value;
        if (!string.IsNullOrEmpty(userObjectId))
        {
            var department = await GetUserDepartment(userObjectId);
            identity?.AddClaim(new Claim("department", department));
        }

        // Example: Add application-specific role
        var email = context.Principal.FindFirst("email")?.Value;
        if (email?.EndsWith("@admin.com") == true)
        {
            identity?.AddClaim(new Claim(ClaimTypes.Role, "SuperAdmin"));
        }
    };
});

Följande kod lägger till anpassade anspråk i en webbapp genom att anropa Microsoft Graph för att hämta ytterligare användarprofildata efter tokenverifiering:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;

builder.Services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme,
    options =>
{
    var existingHandler = options.Events.OnTokenValidated;

    options.Events.OnTokenValidated = async context =>
    {
        // Preserve built-in processing
        await existingHandler(context);

        // Call Microsoft Graph to get additional user data
        var graphClient = context.HttpContext.RequestServices
            .GetRequiredService<GraphServiceClient>();

        var user = await graphClient.Me.GetAsync();

        var identity = context.Principal.Identity as ClaimsIdentity;
        identity?.AddClaim(new Claim("jobTitle", user?.JobTitle ?? ""));
        identity?.AddClaim(new Claim("department", user?.Department ?? ""));
    };
});

Lägga till frågeparametrar i auktoriseringsbegäran

Följande kod lägger till anpassade frågeparametrar i auktoriseringsbegäran som skickas till Microsoft Entra identitetsprovider:

builder.Services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme,
    options =>
{
    var existingHandler = options.Events.OnRedirectToIdentityProvider;

    options.Events.OnRedirectToIdentityProvider = async context =>
    {
        // Preserve existing behavior
        if (existingHandler != null)
        {
            await existingHandler(context);
        }

        // Add custom query parameters
        context.ProtocolMessage.Parameters.Add("slice", "testslice");
        context.ProtocolMessage.Parameters.Add("custom_param", "custom_value");

        // Conditional parameters based on request
        if (context.HttpContext.Request.Query.ContainsKey("prompt"))
        {
            context.ProtocolMessage.Prompt = context.HttpContext.Request.Query["prompt"];
        }
    };
});

Anpassa autentiseringsfelhantering

Följande kod hanterar autentiseringsfel genom att logga felet och returnera ett anpassat JSON-felsvar:

builder.Services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme,
    options =>
{
    options.Events.OnAuthenticationFailed = async context =>
    {
        // Log the error
        var logger = context.HttpContext.RequestServices
            .GetRequiredService<ILogger<Program>>();
        logger.LogError(context.Exception, "Authentication failed");

        // Customize error response
        context.Response.StatusCode = 401;
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync($$"""
            {
                "error": "authentication_failed",
                "error_description": "{{context.Exception.Message}}"
            }
            """);

        context.HandleResponse(); // Suppress default error handling
    };
});

Hantera åtkomst nekad

Följande kod omdirigerar användare till en anpassad sida när de nekar medgivande:

builder.Services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme,
    options =>
{
    options.Events.OnAccessDenied = async context =>
    {
        // User denied consent
        context.Response.Redirect("/Home/AccessDenied");
        context.HandleResponse();
        await Task.CompletedTask;
    };
});

Anpassa tokenförvärv

Du kan anpassa hur token hämtas när du anropar underordnade API:er genom att skicka alternativ till IDownstreamApi.

Använda IDownstreamApi med anpassade alternativ

Följande kod skickar ett korrelations-ID och extra frågeparametrar när du hämtar en token via IDownstreamApi:

using Microsoft.Identity.Abstractions;

public class TodoListController : ControllerBase
{
    private readonly IDownstreamApi _downstreamApi;

    public TodoListController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult> GetTodo(int id, Guid correlationId)
    {
        var result = await _downstreamApi.GetForUserAsync<Todo>(
            "TodoListService",
            options =>
            {
                options.RelativePath = $"api/todolist/{id}";

                // Customize token acquisition
                options.TokenAcquisitionOptions = new TokenAcquisitionOptions
                {
                    CorrelationId = correlationId,
                    ExtraQueryParameters = new Dictionary<string, string>
                    {
                        { "slice", "test_slice" }
                    }
                };
            });

        return Ok(result);
    }
}

Anpassa användargränssnittet

Du kan styra var användarna hamnar efter inloggning och utloggning och anpassa den utloggade upplevelsen.

Omdirigera till en specifik sida efter inloggning

Använd parametern redirectUri för att skicka användare till en specifik sida när de har loggat in:

<!-- Razor view -->
<a href="/MicrosoftIdentity/Account/SignIn?redirectUri=/Dashboard">Sign In</a>

<!-- Or in controller -->
[HttpGet]
public IActionResult SignInToDashboard()
{
    return RedirectToAction("SignIn", "Account", new
    {
        area = "MicrosoftIdentity",
        redirectUri = "/Dashboard"
    });
}

Anpassa den utloggade sidan

Alternativ 1: Åsidosätt Razor-sidan

Skapa en fil på Areas/MicrosoftIdentity/Pages/Account/SignedOut.cshtml med ditt anpassade innehåll:

@page
@model Microsoft.Identity.Web.UI.Areas.MicrosoftIdentity.Pages.Account.SignedOutModel
@{
    ViewData["Title"] = "Signed out";
}

<div class="container text-center mt-5">
    <h1>You have been signed out</h1>
    <p>Thank you for using our application.</p>
    <a asp-area="" asp-controller="Home" asp-action="Index" class="btn btn-primary">
        Return to Home
    </a>
</div>

Alternativ 2: Omdirigera till en anpassad sida

Följande kod omdirigerar användare till en anpassad utloggad sida i stället för standardinställningen:

builder.Services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme,
    options =>
{
    options.Events.OnSignedOutCallbackRedirect = context =>
    {
        context.Response.Redirect("/Home/SignedOut");
        context.HandleResponse();
        return Task.CompletedTask;
    };
});

Anpassa inloggningsupplevelsen

Använda inloggningstips och domäntips

Effektivisera inloggningsupplevelsen genom att fylla i användarnamn i förväg och dirigera användare till specifika Microsoft Entra klienter.

Förstå tips

Tips Avsikt Exempel
loginHint Fyll i fältet användarnamn/e-post i förväg "user@contoso.com"
domainHint Direkt till specifik hyresgästs inloggningssida "contoso.com"

Använd indikationsmönster

Mönster 1: Styrenhetsbaserad

Följande kod visar kontrollantåtgärder för standardinloggning, inloggning med inloggningstips, domäntips eller båda:

using Microsoft.AspNetCore.Mvc;

public class AuthController : Controller
{
    [HttpGet]
    public IActionResult SignIn()
    {
        // Standard sign-in
        return RedirectToAction("SignIn", "Account", new
        {
            area = "MicrosoftIdentity",
            redirectUri = "/Dashboard"
        });
    }

    [HttpGet]
    public IActionResult SignInWithLoginHint()
    {
        // Pre-populate username
        return RedirectToAction("SignIn", "Account", new
        {
            area = "MicrosoftIdentity",
            redirectUri = "/Dashboard",
            loginHint = "user@contoso.com"
        });
    }

    [HttpGet]
    public IActionResult SignInWithDomainHint()
    {
        // Direct to Contoso tenant
        return RedirectToAction("SignIn", "Account", new
        {
            area = "MicrosoftIdentity",
            redirectUri = "/Dashboard",
            domainHint = "contoso.com"
        });
    }

    [HttpGet]
    public IActionResult SignInWithBothHints()
    {
        // Pre-populate AND direct to tenant
        return RedirectToAction("SignIn", "Account", new
        {
            area = "MicrosoftIdentity",
            redirectUri = "/Dashboard",
            loginHint = "user@contoso.com",
            domainHint = "contoso.com"
        });
    }
}

Mönster 2: Vybaserad

Följande HTML visar inloggningslänkar med olika tipskonfigurationer:

<div class="sign-in-options">
    <h2>Sign In Options</h2>

    <!-- Standard sign-in -->
    <a href="/MicrosoftIdentity/Account/SignIn?redirectUri=/Dashboard"
       class="btn btn-primary">
        Sign In
    </a>

    <!-- With login hint -->
    <a href="/MicrosoftIdentity/Account/SignIn?redirectUri=/Dashboard&loginHint=user@contoso.com"
       class="btn btn-secondary">
        Sign In as user@contoso.com
    </a>

    <!-- With domain hint -->
    <a href="/MicrosoftIdentity/Account/SignIn?redirectUri=/Dashboard&domainHint=contoso.com"
       class="btn btn-secondary">
        Sign In (Contoso)
    </a>
</div>

Mönster 3: Programmatiskt med OnRedirectToIdentityProvider

Följande kod anger dynamiskt tips baserat på frågeparametrar och cookies under omdirigeringen till identitetsprovidern:

builder.Services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme,
    options =>
{
    var existingHandler = options.Events.OnRedirectToIdentityProvider;

    options.Events.OnRedirectToIdentityProvider = async context =>
    {
        if (existingHandler != null)
        {
            await existingHandler(context);
        }

        // Add hints based on application logic
        if (context.HttpContext.Request.Query.TryGetValue("tenant", out var tenant))
        {
            context.ProtocolMessage.DomainHint = tenant;
        }

        // Get suggested user from cookie or session
        var suggestedUser = context.HttpContext.Request.Cookies["LastSignedInUser"];
        if (!string.IsNullOrEmpty(suggestedUser))
        {
            context.ProtocolMessage.LoginHint = suggestedUser;
        }
    };
});

Användningsfall

E-handelsplattform:

// Pre-fill returning customer email
loginHint = customerEmail

B2B-applikation:

// Direct to customer's tenant
domainHint = customerDomain

SaaS för flera innehavare:

// Route based on subdomain
domainHint = GetTenantFromSubdomain(Request.Host)

Följ metodtipsen

Saker att göra

1. Bevara alltid befintliga händelsehanterare. Spara och anropa den befintliga hanteraren innan du kör din anpassade logik:

var existingHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
    await existingHandler(context); // Call Microsoft.Identity.Web's handler
    // Your custom code
};

2. Använd korrelations-ID för spårning. Koppla ett korrelations-ID till begäranden om tokenförvärv för diagnostik:

var tokenOptions = new TokenAcquisitionOptions
{
    CorrelationId = Activity.Current?.Id ?? Guid.NewGuid()
};

3. Verifiera anpassade anspråk. Kontrollera att anpassade anspråk innehåller förväntade värden innan du beviljar åtkomst:

var department = context.Principal.FindFirst("department")?.Value;
if (!IsValidDepartment(department))
{
    throw new UnauthorizedAccessException("Invalid department");
}

4. Logga anpassningsfel. Omslut anpassad logik i try-catch-block och logga fel.

try
{
    // Custom logic
}
catch (Exception ex)
{
    logger.LogError(ex, "Custom authentication logic failed");
    throw;
}

5. Testa både lyckade och misslyckade vägar. Gå igenom alla autentiseringsscenarier i dina tester:

// Test with valid tokens
// Test with missing claims
// Test with expired tokens
// Test with wrong audience

Förbud

1. Hoppa inte över Microsoft. Identity.Webs händelsehanterare:

//  Wrong - loses built-in security checks
options.Events.OnTokenValidated = async context => { /* your code */ };

//  Correct - preserves security
var existing = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
    await existing(context);
    /* your code */
};

2. Aktivera inte PII-loggning i produktion:

//  Wrong
options.EnablePiiLogging = true; // In production!

//  Correct
if (builder.Environment.IsDevelopment())
{
    options.EnablePiiLogging = true;
}

3. Kringgå inte tokenverifiering:

//  Wrong - insecure!
options.TokenValidationParameters.ValidateLifetime = false;
options.TokenValidationParameters.ValidateAudience = false;

//  Correct - maintain security
options.TokenValidationParameters.ValidateLifetime = true;
options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(5);

4. Hårdkoda inte känsliga värden:

//  Wrong
options.ClientSecret = "mysecret123";

//  Correct
options.ClientSecret = builder.Configuration["AzureAd:ClientSecret"];

5. Ändra inte autentisering i mellanprogram:

//  Wrong - configure in Startup, not middleware
app.Use(async (context, next) =>
{
    // Modifying auth options here is too late!
});

Felsökning av vanliga problem

Åtgärda att anpassningen inte träder i kraft

Kontrollera körordning:

  1. AddMicrosoftIdentityWebApp / AddMicrosoftIdentityWebApi anger standardvärden
  2. Dina Configure samtal körs
  3. PostConfigure anrop körs (om det finns några)
  4. Alternativ används

Lösning: Använd PostConfigure om samtalet Configure inte börjar gälla eftersom PostConfigure körs efter alla Configure anrop:

services.PostConfigure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme,
    options => { /* your changes */ }
);

Åtgärda saknade anpassade anspråk

Kontrollera följande om anpassade krav inte visas:

  1. Den nya OnTokenValidated-hanteraren är korrekt kedjad med den befintliga hanteraren.
  2. Autentiseringen lyckas innan koden lägger till anspråk.
  3. Anspråk läggs till i rätt ClaimsIdentity.

Följande kod loggar alla anspråk för felsökning:

var claims = context.Principal.Claims.ToList();
logger.LogInformation($"Claims count: {claims.Count}");
foreach (var claim in claims)
{
    logger.LogInformation($"{claim.Type}: {claim.Value}");
}

Åtgärda händelser som inte utlöser

Om händelser inte utlöses, kontrollera att mellanprogrammen för autentisering och auktorisering är registrerade i rätt ordning.

app.UseAuthentication(); // Must be first
app.UseAuthorization();  // Must be second
app.MapControllers();    // Then endpoints