Pas verificatie aan met Microsoft. Identity.Web

Microsoft. Identity.Web biedt veilige standaardinstellingen voor verificatie en autorisatie in ASP.NET Core toepassingen die kunnen worden geïntegreerd met Microsoft Entra ID. U kunt veel aspecten van verificatiegedrag aanpassen met behoud van de ingebouwde beveiligingsfuncties van de bibliotheek.

Aanpasbare gebieden identificeren

Oppervlak Aanpassingsopties
Configuration Alle MicrosoftIdentityOptions, OpenIdConnectOptions, JwtBearerOptions eigenschappen
Gebeurtenissen OpenID Connect-gebeurtenissen (OnTokenValidated, OnRedirectToIdentityProviderenzovoort)
Token ophalen Correlatie-ID's, extra query parameters
Claims Aangepaste claims toevoegen aan ClaimsPrincipal
UI Afmeldingspagina's, omleidingsgedrag
Aanmelden Aanmeldingshints, domeinhints

Een aanpassingsmethode kiezen

De volgende tabel bevat een overzicht van de gebieden die u kunt aanpassen en wat elk gebied ondersteunt.

Gebruik een van de twee benaderingen om opties aan te passen:

  1. Configure<TOptions> - Configureert opties voordat ze worden gebruikt
  2. PostConfigure<TOptions> - Configureert opties na alle Configure aanroepen

Uitvoeringsvolgorde:

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

Verificatieopties configureren

In deze sectie ziet u hoe u de verschillende verificatieoptieklassen configureert die Microsoft. Identity.Web gebruikt.

Inzicht in configuratietoewijzing

De "AzureAd" Sectie in appsettings.json wordt toegewezen aan meerdere klassen:

U kunt elke eigenschap uit deze klassen in uw configuratie gebruiken.

Patroon 1: MicrosoftIdentityOptions configureren

De volgende code past MicrosoftIdentityOptions aan om PII-registratie in te schakelen, clientfuncties in te stellen en de parameters voor de tokenvalidatie aan te passen.

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();

Patroon 2: OpenIdConnectOptions configureren (web-apps)

De volgende code past OpenIdConnectOptions aan voor een web-app om het antwoordtype in te stellen, scopes toe te voegen en instellingen voor cookie- en tokenvalidatie te configureren.

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;
});

Patroon 3: JwtBearerOptions (web-API's) configureren

Met de volgende code wordt JwtBearerOptions een web-API aangepast om geldige doelgroepen, claimtoewijzingen en validatie van levensduur van tokens in te stellen:

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
});

Met de volgende code configureert u het cookiebeleid en de opties voor cookieverificatie voor uw app, inclusief beveiligingsinstellingen en verloopgedrag:

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;
});

Gebeurtenishandlers aanpassen

OpenID Connect en JWT Bearer-verificatie maken gebeurtenissen beschikbaar waarmee u verbinding kunt maken. Microsoft. Identity.Web stelt zijn eigen gebeurtenis-handlers in, dus u moet uw aangepaste handlers koppelen aan de bestaande handlers om de ingebouwde functionaliteit te behouden.

Bestaande handlers behouden

Wanneer u aangepaste gebeurtenis-handlers toevoegt, slaat u de bestaande handler altijd eerst op en roept u deze aan. In het volgende voorbeeld ziet u de verkeerde en juiste benaderingen.

De volgende code onjuist overschrijft de Microsoft.Identity.Web-handler:

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

De volgende code is goed gekoppeld aan de bestaande handler:

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"));
    };
});

Veelvoorkomende gebeurtenisscenario's toepassen

Aangepaste claims toevoegen na tokenvalidatie

Met de volgende code worden na de tokenvalidatie aangepaste claims toegevoegd aan de ClaimsPrincipal in een web-API. Hiermee wordt de afdeling van de gebruiker uit een database opgezoekt en wordt een toepassingsspecifieke rol toegewezen op basis van het e-maildomein:

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"));
        }
    };
});

Met de volgende code worden aangepaste claims in een web-app toegevoegd door Microsoft Graph aan te roepen om extra gebruikersprofielgegevens op te halen na de validatie van het token:

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 ?? ""));
    };
});

Queryparameters toevoegen aan autorisatieaanvraag

Met de volgende code worden aangepaste queryparameters toegevoegd aan de autorisatieaanvraag die naar de Microsoft Entra id-provider is verzonden:

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"];
        }
    };
});

Aanpassen van verwerking bij authenticatiefouten

De volgende code verwerkt verificatiefouten door de fout te registreren en een aangepast JSON-foutantwoord te retourneren:

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
    };
});

Toegang geweigerd afhandelen

Met de volgende code worden gebruikers omgeleid naar een aangepaste pagina wanneer ze toestemming weigeren:

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;
    };
});

Tokenaanwerving aanpassen

U kunt aanpassen hoe tokens worden verkregen bij het aanroepen van downstream-API's door opties door te geven aan IDownstreamApi.

IDownstreamApi gebruiken met aangepaste opties

De volgende code geeft een correlatie-id en extra queryparameters door bij het verkrijgen van een 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);
    }
}

De gebruikersinterface aanpassen

U kunt bepalen waar gebruikers terechtkomen na aanmelding en afmelden en de ervaring voor afmelden aanpassen.

Omleiden naar een specifieke pagina na aanmelding

Gebruik de redirectUri parameter om gebruikers naar een specifieke pagina te verzenden nadat ze zich hebben aangemeld:

<!-- 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"
    });
}

De uitlogpagina aanpassen

Optie 1: De Razor-pagina overschrijven

Maak een bestand op Areas/MicrosoftIdentity/Pages/Account/SignedOut.cshtml met uw aangepaste inhoud:

@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>

Optie 2: Omleiden naar een aangepaste pagina

Met de volgende code worden gebruikers omgeleid naar een aangepaste afgemelde pagina in plaats van de standaardpagina:

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

De aanmeldingservaring aanpassen

Aanmeldingshints en domeinhints gebruiken

Stroomlijn de aanmeldingservaring door gebruikersnamen vooraf in te vullen en gebruikers door te leiden naar specifieke Microsoft Entra tenants.

Hints begrijpen

Tip Purpose Voorbeeld
loginHint Vooraf ingevuld veld gebruikersnaam/e-mail "user@contoso.com"
domainHint Direct naar specifieke tenantaanmeldingspagina "contoso.com"

Hintpatronen toepassen

Patroon 1: Op controller gebaseerd

De volgende code toont controlleracties voor standaardaanmelding, aanmelding met een hint voor aanmelding, domeinhint of beide:

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"
        });
    }
}

Patroon 2: Weergave-gebaseerd

In de volgende HTML worden inlogkoppelingen met verschillende hintconfiguraties getoond.

<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>

Patroon 3: Programmatisch met OnRedirectToIdentityProvider

Met de volgende code worden hints dynamisch ingesteld op basis van queryparameters en cookies tijdens de omleiding naar de id-provider:

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;
        }
    };
});

Gebruikssituaties

E-commerceplatform:

// Pre-fill returning customer email
loginHint = customerEmail

B2B-toepassing:

// Direct to customer's tenant
domainHint = customerDomain

SaaS-model voor meerdere tenants:

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

Best practices volgen

Aanbevelingen

1. Behoud altijd bestaande eventhandlers. Sla de bestaande handler op en roep deze aan voordat u uw aangepaste logica uitvoert:

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

2. Gebruik correlatie-id's voor tracering. Koppel een correlatie-id aan tokenverwervingsaanvragen voor diagnostische gegevens:

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

3. Valideer aangepaste claims. Controleer of aangepaste claims verwachte waarden bevatten voordat u toegang verleent:

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

4. Fouten bij het aanpassen van logboeken. Aangepaste logica verpakken in try-catch-blokken en logboekfouten:

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

5. Test zowel geslaagde als mislukte paden. Alle verificatiescenario's in uw tests behandelen:

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

Dingen die je niet moet doen

1. Sla de gebeurtenishandlers van Microsoft.Identity.Web niet over:

//  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. Schakel logging van persoonlijk identificeerbare informatie (PII) niet in in productie:

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

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

3. Tokenvalidatie niet omzeilen:

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

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

4. Geen gevoelige waarden coderen:

//  Wrong
options.ClientSecret = "mysecret123";

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

5. Wijzig de verificatie niet in middleware:

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

Veelvoorkomende problemen oplossen

Aanpassing wordt niet van kracht

Uitvoeringsvolgorde controleren:

  1. AddMicrosoftIdentityWebApp / AddMicrosoftIdentityWebApi stelt standaardinstellingen in
  2. Uw Configure oproepen worden uitgevoerd
  3. PostConfigure oproepen uitvoeren (indien aanwezig)
  4. Opties worden gebruikt

Oplossing: Gebruik PostConfigure deze optie als uw Configure oproep niet van kracht wordt, omdat PostConfigure deze wordt uitgevoerd na alle Configure oproepen:

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

Ontbrekende aangepaste claims herstellen

Controleer het volgende als aangepaste claims niet worden weergegeven:

  1. De OnTokenValidated handler wordt correct gekoppeld aan de bestaande handler.
  2. Verificatie slaagt voordat uw code claims toevoegt.
  3. Claims worden toegevoegd aan het juiste ClaimsIdentity.

Met de volgende code worden alle claims voor foutopsporing vastgelegd:

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

Niet afgaande gebeurtenissen oplossen

Als gebeurtenissen niet worden geactiveerd, controleer dan of de verificatie- en autorisatie-middleware in de juiste volgorde zijn geregistreerd.

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