Delen via


ASP.NET Core server-side en Blazor Web App aanvullende beveiligingsscenario's

Notitie

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Belangrijk

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikelvoor de huidige release.

In dit artikel wordt uitgelegd hoe u Blazor aan de serverzijde configureert voor aanvullende beveiligingsscenario's, waaronder het doorgeven van tokens aan een Blazor-app.

Notitie

De codevoorbeelden in dit artikel gebruiken nullable reference types (NRT's) en .NET compiler null-state statische analyse, die worden ondersteund in ASP.NET Core in .NET 6 of hoger. Wanneer u ASP.NET Core 5.0 of eerder als doel hebt, verwijdert u de null-typeaanduiding (?) uit de string?, TodoItem[]?, WeatherForecast[]?en IEnumerable<GitHubBranch>? typen in de voorbeelden van het artikel.

Tokens doorgeven aan een Blazor-app aan de serverzijde

Voor Blazor Web Appmoeten richtlijnen voor het doorgeven van tokens aan Razor onderdelen worden behandeld in de documentatie (dotnet/AspNetCore.Docs #31691) in 2025 voor een preview-versie van .NET 10.

Zie de volgende problemen voor meer informatie:

Voor Blazor Server, bekijk de 7.0-versie van deze artikelsectie.

Tokens die buiten de Razor onderdelen in een Blazor-app aan de serverzijde beschikbaar zijn, kunnen worden doorgegeven aan onderdelen met de methode die in deze sectie wordt beschreven. Het voorbeeld in deze sectie is gericht op het doorgeven van toegangs-, vernieuwings- en XSRF-token (ANTI-request forgery) tokens aan de Blazor-app, maar de methode is geldig voor andere HTTP-contextstatussen.

Notitie

Het doorgeven van het XSRF-token aan Razor onderdelen is handig in scenario's waarin onderdelen POST naar Identity of andere eindpunten die validatie vereisen. Als voor uw app alleen toegangs- en vernieuwingstokens zijn vereist, kunt u de XSRF-tokencode uit het volgende voorbeeld verwijderen.

Verifieer de app zoals u dat zou doen met een gewone Razor Pages- of MVC-app. Verstrek de tokens en sla deze op in de authenticatie cookie.

In het bestand Program:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

builder.Services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});

In Startup.cs:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});

In Startup.cs:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});

Optioneel kunnen extra bereiken worden toegevoegd met options.Scope.Add("{SCOPE}");, waarbij de tijdelijke aanduiding {SCOPE} het extra bereik is dat moet worden toegevoegd.

Definieer een tokenproviderservice die kan worden gebruikt in de Blazor-app om de tokens op te lossen van afhankelijkheidsinjectie (DI).

TokenProvider.cs:

public class TokenProvider
{
    public string? AccessToken { get; set; }
    public string? RefreshToken { get; set; }
    public string? XsrfToken { get; set; }
}

Voeg in het bestand Program services toe voor:

  • IHttpClientFactory: wordt gebruikt in een WeatherForecastService-klasse die weergegevens ophaalt uit een server-API met een toegangstoken.
  • TokenProvider: bevat de toegangs- en vernieuwingstokens.
builder.Services.AddHttpClient();
builder.Services.AddScoped<TokenProvider>();

Voeg in Startup.ConfigureServices van Startup.csdiensten toe voor:

  • IHttpClientFactory: wordt gebruikt in een WeatherForecastService-klasse die weergegevens ophaalt uit een server-API met een toegangstoken.
  • TokenProvider: bevat de toegangs- en vernieuwingstokens.
services.AddHttpClient();
services.AddScoped<TokenProvider>();

Definieer een klasse om de initiële app-status door te geven met de toegangs- en vernieuwingstokens.

InitialApplicationState.cs:

public class InitialApplicationState
{
    public string? AccessToken { get; set; }
    public string? RefreshToken { get; set; }
    public string? XsrfToken { get; set; }
}

Maak en exemplaar van InitialApplicationState in het Pages/_Host.cshtml-bestand en geef het als parameter door aan de app:

Maak en exemplaar van InitialApplicationState in het Pages/_Layout.cshtml-bestand en geef het als parameter door aan de app:

Maak en exemplaar van InitialApplicationState in het Pages/_Host.cshtml-bestand en geef het als parameter door aan de app:

@using Microsoft.AspNetCore.Authentication
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf

...

@{
    var tokens = new InitialApplicationState
    {
        AccessToken = await HttpContext.GetTokenAsync("access_token"),
        RefreshToken = await HttpContext.GetTokenAsync("refresh_token"),
        XsrfToken = Xsrf.GetAndStoreTokens(HttpContext).RequestToken
    };
}

<component ... param-InitialState="tokens" ... />

Los in het App-onderdeel (App.razor) de service op en initialiseer deze met de gegevens uit de parameter:

@inject TokenProvider TokenProvider

...

@code {
    [Parameter]
    public InitialApplicationState? InitialState { get; set; }

    protected override Task OnInitializedAsync()
    {
        TokenProvider.AccessToken = InitialState?.AccessToken;
        TokenProvider.RefreshToken = InitialState?.RefreshToken;
        TokenProvider.XsrfToken = InitialState?.XsrfToken;

        return base.OnInitializedAsync();
    }
}

Notitie

Een alternatief voor het toewijzen van de initiële status aan de TokenProvider in het vorige voorbeeld is het kopiëren van de gegevens naar een gescopeerde service binnen OnInitializedAsync voor gebruik in de hele app.

Voeg een pakketreferentie toe aan de app voor het Microsoft.AspNet.WebApi.Client NuGet-pakket.

Notitie

Zie de artikelen onder Pakketten installeren en beheren in NuGet-documentatie (NuGet-documentatie) voor hulp bij het toevoegen van pakketten aan .NET-apps. Bevestig de juiste pakketversies op NuGet.org.

In de service die een beveiligde API-aanvraag maakt, injecteert u de tokenprovider en haalt u het token voor de API-aanvraag op:

WeatherForecastService.cs:

using System;
using System.Net.Http;
using System.Threading.Tasks;

public class WeatherForecastService
{
    private readonly HttpClient http;
    private readonly TokenProvider tokenProvider;

    public WeatherForecastService(IHttpClientFactory clientFactory, 
        TokenProvider tokenProvider)
    {
        http = clientFactory.CreateClient();
        this.tokenProvider = tokenProvider;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        var token = tokenProvider.AccessToken;
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://localhost:5003/WeatherForecast");
        request.Headers.Add("Authorization", $"Bearer {token}");
        var response = await http.SendAsync(request);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync<WeatherForecast[]>() ?? 
            Array.Empty<WeatherForecast>();
    }
}

Voor een XSRF-token dat is doorgegeven aan een onderdeel, injecteert u de TokenProvider en voegt u het XSRF-token toe aan de POST-aanvraag. In het volgende voorbeeld wordt het token toegevoegd aan een afmeldingseindpunt POST. Het scenario voor het volgende voorbeeld is dat het afmeldingseindpunt (Areas/Identity/Pages/Account/Logout.cshtml, in de app) geen IgnoreAntiforgeryTokenAttribute (@attribute [IgnoreAntiforgeryToken]) opgeeft omdat er een actie wordt uitgevoerd naast een normale afmeldingsbewerking die moet worden beveiligd. Voor het eindpunt is een geldig XSRF-token vereist om de aanvraag te verwerken.

In een component dat een Logout-knop presenteert aan geautoriseerde gebruikers:

@inject TokenProvider TokenProvider

...

<AuthorizeView>
    <Authorized>
        <form action="/Identity/Account/Logout?returnUrl=%2F" method="post">
            <button class="nav-link btn btn-link" type="submit">Logout</button>
            <input name="__RequestVerificationToken" type="hidden" 
                value="@TokenProvider.XsrfToken">
        </form>
    </Authorized>
    <NotAuthorized>
        ...
    </NotAuthorized>
</AuthorizeView>

Het verificatieschema instellen

Voor een app die meer dan één verificatie-middleware gebruikt en dus meer dan één verificatieschema heeft, kan het schema dat Blazor gebruikt expliciet worden ingesteld in de eindpuntconfiguratie van het Program-bestand. In het volgende voorbeeld wordt het OIDC-schema (OpenID Connect) ingesteld:

Voor een app die gebruikmaakt van meer dan één verificatie-middleware en dus meer dan één verificatieschema heeft, kan het schema dat Blazor gebruikt expliciet worden ingesteld in de eindpuntconfiguratie van Startup.cs. In het volgende voorbeeld wordt het OIDC-schema (OpenID Connect) ingesteld:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;

...

app.MapRazorComponents<App>().RequireAuthorization(
    new AuthorizeAttribute
    {
        AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
    })
    .AddInteractiveServerRenderMode();
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

...

app.MapBlazorHub().RequireAuthorization(
    new AuthorizeAttribute 
    {
        AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
    });

Voor een app die gebruikmaakt van meer dan één verificatie-middleware en dus meer dan één verificatieschema heeft, kan het schema dat Blazor gebruikt expliciet worden ingesteld in de eindpuntconfiguratie van Startup.Configure. In het volgende voorbeeld wordt het Microsoft Entra ID-schema ingesteld:

endpoints.MapBlazorHub().RequireAuthorization(
    new AuthorizeAttribute 
    {
        AuthenticationSchemes = AzureADDefaults.AuthenticationScheme
    });

OpenID Connect -eindpunten (OIDC) v2.0 gebruiken

In versies van ASP.NET Core vóór 5.0 gebruiken de verificatiebibliotheek en Blazor-sjablonen OpenID Connect (OIDC) v1.0-eindpunten. Als u een v2.0-eindpunt wilt gebruiken met versies van ASP.NET Core vóór 5.0, configureert u de optie OpenIdConnectOptions.Authority in de OpenIdConnectOptions:

services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, 
    options =>
    {
        options.Authority += "/v2.0";
    }

U kunt de instelling ook maken in het app-instellingenbestand (appsettings.json):

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
    ...
  }
}

Als het toevoegen van een segment aan de instantie niet geschikt is voor de OIDC-provider van de app, zoals bij niet-ME-ID-providers, stelt u de eigenschap Authority rechtstreeks in. Stel de eigenschap in OpenIdConnectOptions of in het app-instellingenbestand in met de Authority sleutel.

Codewijzigingen

  • De lijst met claims in het id-token wordt gewijzigd voor v2.0-eindpunten. Microsoft-documentatie over de wijzigingen is niet langer beschikbaar, maar richtlijnen voor de claims in een ID-token zijn beschikbaar in de ID-tokenclaimsreferentie.

  • Aangezien resources zijn opgegeven in scope-URI's voor v2.0-eindpunten, verwijdert u de instelling van de eigenschap OpenIdConnectOptions.Resource in OpenIdConnectOptions:

    services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options => 
        {
            ...
            options.Resource = "...";    // REMOVE THIS LINE
            ...
        }
    

App-ID-URI

  • Wanneer u v2.0-eindpunten gebruikt, definiëren API's een App ID URI, die bedoeld is om een unieke id voor de API weer te geven.
  • Alle bereiken omvatten de app-id-URI als voorvoegsel en v2.0-eindpunten verzenden toegangstokens met de app-id-URI als doelgroep.
  • Wanneer u V2.0-eindpunten gebruikt, wordt de client-id die is geconfigureerd in de Server-API gewijzigd van de API-toepassings-id (client-id) in de app-id-URI.

appsettings.json:

{
  "AzureAd": {
    ...
    "ClientId": "https://{TENANT}.onmicrosoft.com/{PROJECT NAME}"
    ...
  }
}

U vindt de app-id-URI die u kunt gebruiken in de beschrijving van de registratie van de OIDC-provider-app.

Circuithandler voor het vastleggen van gebruikers voor aangepaste services

Gebruik een CircuitHandler om een gebruiker op te slaan uit de AuthenticationStateProvider en de gebruiker in een dienst onderbrengen. Als u de gebruiker wilt bijwerken, registreer dan een callback naar AuthenticationStateChanged en plaats een Task in de wachtrij om de nieuwe gebruiker te verkrijgen en de service bij te werken. In het volgende voorbeeld ziet u de benadering.

In het volgende voorbeeld:

  • OnConnectionUpAsync wordt aangeroepen telkens wanneer het circuit opnieuw verbinding maakt, waarbij de gebruiker de levensduur van de verbinding instelt. Alleen de OnConnectionUpAsync methode is vereist, tenzij u updates implementeert via een handler voor verificatiewijzigingen (AuthenticationChanged in het volgende voorbeeld).
  • OnCircuitOpenedAsync wordt aangeroepen om de gewijzigde handler voor verificatie, AuthenticationChanged, te koppelen om de gebruiker bij te werken.
  • Het catch blok van de UpdateAuthentication-taak neemt geen actie op uitzonderingen omdat er op dit moment geen manier is om de uitzonderingen te rapporteren bij het uitvoeren van code. Als er een foutmelding wordt gegenereerd vanuit de taak, wordt de foutmelding op een andere plek in de app gerapporteerd.

UserService.cs:

using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class UserService
{
    private ClaimsPrincipal currentUser = new(new ClaimsIdentity());

    public ClaimsPrincipal GetUser() => currentUser;

    internal void SetUser(ClaimsPrincipal user)
    {
        if (currentUser != user)
        {
            currentUser = user;
        }
    }
}

internal sealed class UserCircuitHandler(
        AuthenticationStateProvider authenticationStateProvider,
        UserService userService) 
        : CircuitHandler, IDisposable
{
    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        authenticationStateProvider.AuthenticationStateChanged += 
            AuthenticationChanged;

        return base.OnCircuitOpenedAsync(circuit, cancellationToken);
    }

    private void AuthenticationChanged(Task<AuthenticationState> task)
    {
        _ = UpdateAuthentication(task);

        async Task UpdateAuthentication(Task<AuthenticationState> task)
        {
            try
            {
                var state = await task;
                userService.SetUser(state.User);
            }
            catch
            {
            }
        }
    }

    public override async Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        var state = await authenticationStateProvider.GetAuthenticationStateAsync();
        userService.SetUser(state.User);
    }

    public void Dispose()
    {
        authenticationStateProvider.AuthenticationStateChanged -= 
            AuthenticationChanged;
    }
}
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class UserService
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());

    public ClaimsPrincipal GetUser()
    {
        return currentUser;
    }

    internal void SetUser(ClaimsPrincipal user)
    {
        if (currentUser != user)
        {
            currentUser = user;
        }
    }
}

internal sealed class UserCircuitHandler : CircuitHandler, IDisposable
{
    private readonly AuthenticationStateProvider authenticationStateProvider;
    private readonly UserService userService;

    public UserCircuitHandler(
        AuthenticationStateProvider authenticationStateProvider,
        UserService userService)
    {
        this.authenticationStateProvider = authenticationStateProvider;
        this.userService = userService;
    }

    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        authenticationStateProvider.AuthenticationStateChanged += 
            AuthenticationChanged;

        return base.OnCircuitOpenedAsync(circuit, cancellationToken);
    }

    private void AuthenticationChanged(Task<AuthenticationState> task)
    {
        _ = UpdateAuthentication(task);

        async Task UpdateAuthentication(Task<AuthenticationState> task)
        {
            try
            {
                var state = await task;
                userService.SetUser(state.User);
            }
            catch
            {
            }
        }
    }

    public override async Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        var state = await authenticationStateProvider.GetAuthenticationStateAsync();
        userService.SetUser(state.User);
    }

    public void Dispose()
    {
        authenticationStateProvider.AuthenticationStateChanged -= 
            AuthenticationChanged;
    }
}

In het bestand Program:

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;

...

builder.Services.AddScoped<UserService>();
builder.Services.TryAddEnumerable(
    ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());

In Startup.ConfigureServices van Startup.cs:

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;

...

services.AddScoped<UserService>();
services.TryAddEnumerable(
    ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());

Gebruik de service in een onderdeel om de gebruiker te verkrijgen:

@inject UserService UserService

<h1>Hello, @(UserService.GetUser().Identity?.Name ?? "world")!</h1>

Als u de gebruiker wilt instellen in middleware voor MVC, Razor Pages en in andere ASP.NET Core-scenario's, roept u SetUser aan op de UserService in aangepaste middleware nadat de verificatie-middleware is uitgevoerd of stelt u de gebruiker in met een IClaimsTransformation-implementatie. In het volgende voorbeeld wordt de middleware-benadering gebruikt.

UserServiceMiddleware.cs:

public class UserServiceMiddleware
{
    private readonly RequestDelegate next;

    public UserServiceMiddleware(RequestDelegate next)
    {
        this.next = next ?? throw new ArgumentNullException(nameof(next));
    }

    public async Task InvokeAsync(HttpContext context, UserService service)
    {
        service.SetUser(context.User);
        await next(context);
    }
}

Onmiddellijk voordat de aanroep naar app.MapRazorComponents<App>() in het bestand Program, roept u de middleware aan:

Onmiddellijk voordat de aanroep naar app.MapBlazorHub() in het bestand Program, roept u de middleware aan:

Roep de middleware aan vlak voordat je de oproep naar app.MapBlazorHub() in Startup.Configure van Startup.csdoet:

app.UseMiddleware<UserServiceMiddleware>();

Toegang tot AuthenticationStateProvider in middleware voor uitgaande aanvragen

De AuthenticationStateProvider van een DelegatingHandler voor HttpClient die met IHttpClientFactory is gemaakt, kan worden benaderd in middleware voor uitgaande verzoeken via een circuitactiviteitshandler.

Notitie

Zie de volgende secties van HTTP-aanvragen maken met behulp van IHttpClientFactory in ASP.NET Core-voor algemene richtlijnen voor het definiëren van de delegeringshandlers voor HTTP-aanvragen door HttpClient instanties die zijn gemaakt met behulp van IHttpClientFactory in ASP.NET Core-apps:

In het volgende voorbeeld wordt AuthenticationStateProvider gebruikt om een aangepaste gebruikersnaamheader toe te voegen voor geverifieerde gebruikers aan uitgaande aanvragen.

Implementeer eerst de CircuitServicesAccessor-klasse in de volgende sectie van het artikel Blazor afhankelijkheidsinjectie (DI):

Toegang tot server-side Blazor services vanuit een andere DI-scope

Gebruik de CircuitServicesAccessor voor toegang tot de AuthenticationStateProvider in de DelegatingHandler-implementatie.

AuthenticationStateHandler.cs:

public class AuthenticationStateHandler(
    CircuitServicesAccessor circuitServicesAccessor) 
    : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var authStateProvider = circuitServicesAccessor.Services
            .GetRequiredService<AuthenticationStateProvider>();
        var authState = await authStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;

        if (user.Identity is not null && user.Identity.IsAuthenticated)
        {
            request.Headers.Add("X-USER-IDENTITY-NAME", user.Identity.Name);
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

Registreer in het Program bestand de AuthenticationStateHandler en voeg de handler toe aan de IHttpClientFactory waarmee HttpClient exemplaren worden gemaakt:

builder.Services.AddTransient<AuthenticationStateHandler>();

builder.Services.AddHttpClient("HttpMessageHandler")
    .AddHttpMessageHandler<AuthenticationStateHandler>();