Condividi tramite


Scenari di sicurezza lato server di ASP.NET Core e Blazor Web App scenari di sicurezza aggiuntivi

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Questo articolo illustra come configurare il lato Blazor server per scenari di sicurezza aggiuntivi, tra cui come passare token a un'app Blazor .

Nota

Gli esempi di codice in questo articolo adottano tipi di riferimento annullabile (NRT) e l'analisi statica dello stato di null del compilatore .NET, supportati in ASP.NET Core a partire da .NET 6. Quando si prende di mira .NET 5 o versioni precedenti, rimuovere la designazione di tipo null (?) dai tipi string?, TodoItem[]?, WeatherForecast[]? e IEnumerable<GitHubBranch>? negli esempi dell'articolo.

Passare i token a un'applicazione lato Blazor server

Questa sezione si applica a Blazor Web Apps. Per Blazor Server, vedere la versione .NET 7 di questo articolo.

Se si vogliono usare semplicemente token di accesso per effettuare chiamate API Web da un Blazor Web App con un client HTTP denominato, vedere la sezione Usare un gestore di token per le chiamate API Web , che spiega come usare un'implementazione DelegatingHandler per associare il token di accesso di un utente alle richieste in uscita. Le indicazioni seguenti in questa sezione sono destinate agli sviluppatori che necessitano di token di accesso, token di aggiornamento e altre proprietà di autenticazione lato server per altri scopi.

Per salvare i token e altre proprietà di autenticazione per l'uso lato server in Blazor Web Apps, è consigliabile usare IHttpContextAccessor/HttpContext (IHttpContextAccessor, HttpContext). La lettura di token da HttpContext, compresa come parametro a catena, utilizzando IHttpContextAccessor è supportata per ottenere i token da usare durante il rendering interattivo del server, se i token vengono ottenuti durante il rendering statico sul lato server (SSR statico) o il prerendering. Tuttavia, i token non vengono aggiornati se l'utente esegue l'autenticazione dopo aver stabilito il circuito, poiché HttpContext viene acquisito all'inizio della SignalR connessione. ** Inoltre, l'uso di AsyncLocal<T> da parte di IHttpContextAccessor significa che è necessario prestare attenzione a non perdere il contesto di esecuzione prima di leggere HttpContext. Per altre informazioni, vedere IHttpContextAccessor/HttpContext nelle app ASP.NET Core Blazor.

In una classe del servizio ottenere l'accesso ai membri dello spazio dei nomi Microsoft.AspNetCore.Authentication per visualizzare il GetTokenAsync metodo su HttpContext. Un approccio alternativo, commentato nel seguente esempio, consiste nel chiamare AuthenticateAsync su HttpContext. Per l'oggetto restituito AuthenticateResult.Properties, chiamare GetTokenValue.

using Microsoft.AspNetCore.Authentication;

public class AuthenticationProcessor(IHttpContextAccessor httpContextAccessor)
{
    public async Task<string?> GetAccessToken()
    {
        if (httpContextAccessor.HttpContext is null)
        {
            throw new Exception("HttpContext not available");
        }

        // Approach 1: Call 'GetTokenAsync'
        var accessToken = await httpContextAccessor.HttpContext
            .GetTokenAsync("access_token");

        // Approach 2: Authenticate the user and call 'GetTokenValue'
        /*
        var authResult = await httpContextAccessor.HttpContext.AuthenticateAsync();
        var accessToken = authResult?.Properties?.GetTokenValue("access_token");
        */

        return accessToken;
    }
}

Il servizio viene registrato nel file del progetto server Program :

builder.Services.AddScoped<AuthenticationProcessor>();

AuthenticationProcessor può essere inserito nei servizi lato server, ad esempio in un DelegatingHandler per un HttpClient preconfigurato. L'esempio seguente è solo a scopo dimostrativo o nel caso in cui sia necessario eseguire un'elaborazione speciale nel AuthenticationProcessor servizio perché è sufficiente inserire IHttpContextAccessor e ottenere il token direttamente per chiamare API Web esterne . Per altre informazioni sull'uso IHttpContextAccessor diretto per chiamare le API Web, vedere la sezione Usare un gestore di token per le chiamate API Web .

using System.Net.Http.Headers;

public class TokenHandler(AuthenticationProcessor authProcessor) : 
    DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var accessToken = authProcessor.GetAccessToken();

        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", accessToken);

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

Il gestore di token viene registrato e funge da gestore di delega per un client HTTP denominato nel Program file:

builder.Services.AddHttpContextAccessor();

builder.Services.AddScoped<TokenHandler>();

builder.Services.AddHttpClient("ExternalApi",
      client => client.BaseAddress = new Uri(builder.Configuration["ExternalApiUri"] ?? 
          throw new Exception("Missing base address!")))
      .AddHttpMessageHandler<TokenHandler>();

Attenzione

Assicurarsi che i token non vengano mai trasmessi e gestiti dal client (il .Client progetto), ad esempio in un componente che utilizza Interactive Auto rendering ed è sottoposto a rendering sul client o tramite un servizio lato client. Chiedere sempre al client di chiamare il server (progetto) per elaborare le richieste con token. I token e altri dati di autenticazione non devono mai lasciare il server.

Per i componenti interattivi di Auto, vedere ASP.NET Autenticazione e autorizzazione di baseBlazor, che illustra come lasciare i token di accesso e altre proprietà di autenticazione nel server. Prendere in considerazione anche l'adozione del modello BFF (Backend-for-Frontend), che adotta una struttura di chiamata simile ed è descritto in Proteggere un ASP.NET Core Blazor Web App con OpenID Connect (OIDC) per i provider OIDC e Proteggere un ASP.NET Core Blazor Web App con Microsoft Entra ID for MicrosoftIdentity Web con Entra.

Usare un gestore di token per le chiamate API Web

L'approccio seguente è volto a collegare il token di accesso di un utente nelle richieste in uscita, in particolare per effettuare chiamate alle API Web ad app per le API Web esterne. L'approccio viene illustrato per un Blazor Web App che adotta il rendering globale di Interactive Server, ma lo stesso approccio generale si applica a Blazor Web App che adotta la modalità di rendering interattivo automatico globale. Un concetto importante da tenere presente è che l'accesso al HttpContext utilizzando il IHttpContextAccessor viene eseguito solo sul server.

Per una dimostrazione delle linee guida in questa sezione, vedere le app di esempio BlazorWebAppOidc e BlazorWebAppOidcServer (.NET 8 o versione successiva) nel repository GitHub degli esempi Blazor. Gli esempi adottano una modalità di rendering interattiva globale e l'autenticazione OIDC con Microsoft Entra senza usare pacchetti specifici di Entra. Gli esempi illustrano come passare un token di accesso JWT per chiamare un'API Web sicura.

Microsoft Identity Platform con i pacchetti Web Microsoft per IdentityMicrosoft Entra ID fornisce un'API per chiamare le API web da Blazor Web Apps, con gestione e rinnovo automatici dei token. Per ulteriori informazioni, vedere Proteggere una applicazione ASP.NET Core Blazor Web App con Microsoft Entra ID e le app di esempio BlazorWebAppEntra e BlazorWebAppEntraBff (.NET 9 o versione successiva) nel repository GitHub di esempi Blazor.

Sottoclasse DelegatingHandler per associare il token di accesso di un utente alle richieste in uscita. Il gestore di token viene eseguito solo nel server, quindi l'uso HttpContext è sicuro.

TokenHandler.cs:

using System.Net.Http.Headers;
using Microsoft.AspNetCore.Authentication;

public class TokenHandler(IHttpContextAccessor httpContextAccessor) : 
    DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (httpContextAccessor.HttpContext is null)
        {
            throw new Exception("HttpContext not available");
        }

        var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");

        if (accessToken is null)
        {
            throw new Exception("No access token");
        }

        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", accessToken);

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

Nota

Per indicazioni su come accedere a un AuthenticationStateProvider da un DelegatingHandler, vedere la sezione Accesso al AuthenticationStateProvider nel middleware delle richieste in uscita.

Nel file del Program progetto il gestore di token (TokenHandler) viene registrato come servizio con ambito e specificato come gestore messaggi del client HTTP denominato con AddHttpMessageHandler.

Nell'esempio seguente il {HTTP CLIENT NAME} segnaposto è il nome dell'oggetto HttpCliente il {BASE ADDRESS} segnaposto è l'URI dell'indirizzo di base dell'API Web. Per altre informazioni su AddHttpContextAccessor, vedere IHttpContextAccessor/HttpContext nelle app ASP.NET CoreBlazor.

In Program.cs:

builder.Services.AddHttpContextAccessor();

builder.Services.AddScoped<TokenHandler>();

builder.Services.AddHttpClient("{HTTP CLIENT NAME}",
      client => client.BaseAddress = new Uri("{BASE ADDRESS}"))
      .AddHttpMessageHandler<TokenHandler>();

Esempio:

builder.Services.AddScoped<TokenHandler>();

builder.Services.AddHttpClient("ExternalApi",
      client => client.BaseAddress = new Uri("https://localhost:7277"))
      .AddHttpMessageHandler<TokenHandler>();

È possibile specificare l'indirizzo di base del client HTTP dalla configurazione con builder.Configuration["{CONFIGURATION KEY}"], dove il {CONFIGURATION KEY} segnaposto è la chiave di configurazione:

new Uri(builder.Configuration["ExternalApiUri"] ?? throw new IOException("No URI!"))

In appsettings.json, specifica il ExternalApiUri. L'esempio seguente imposta il valore sull'indirizzo localhost dell'API Web esterna su https://localhost:7277:

"ExternalApiUri": "https://localhost:7277"

A questo punto, un oggetto HttpClient creato da un componente può effettuare richieste API Web sicure. Nell'esempio seguente, {REQUEST URI} è l'URI della richiesta relativa e il {HTTP CLIENT NAME} segnaposto è il nome dell'oggetto HttpClient:

using var request = new HttpRequestMessage(HttpMethod.Get, "{REQUEST URI}");
var client = ClientFactory.CreateClient("{HTTP CLIENT NAME}");
using var response = await client.SendAsync(request);

Esempio:

using var request = new HttpRequestMessage(HttpMethod.Get, "/weather-forecast");
var client = ClientFactory.CreateClient("ExternalApi");
using var response = await client.SendAsync(request);

Sono pianificate funzionalità aggiuntive per Blazor, che vengono rilevate da Access AuthenticationStateProvider nel middleware delle richieste in uscita (dotnet/aspnetcore #52379).. Il problema relativo alla fornitura di token di accesso a HttpClient in modalità Server interattivo (dotnet/aspnetcore #52390) è un problema chiuso che contiene discussioni utili e potenziali strategie alternative per i casi d'uso avanzati.

I token disponibili al di fuori dei componenti in un'applicazione lato server possono essere passati ai componenti con l'approccio descritto in questa sezione. L'esempio di questa sezione è incentrato sul passaggio di token di accesso, aggiornamento e anti-contraffazione di richiesta (XSRF) all'app , ma l'approccio è valido per altri stati del contesto HTTP.

Nota

Il passaggio del token XSRF ai Razor componenti è utile negli scenari in cui i componenti POST a Identity o altri endpoint che richiedono la convalida. Se l'app richiede solo token di accesso e aggiornamento, è possibile rimuovere il codice del token XSRF dall'esempio seguente.

Autenticate l'app come fareste con una normale app Razor Pages o MVC. Effettuare il provisioning e salvare i token per l'autenticazione cookie.

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

Facoltativamente, vengono aggiunti altri ambiti con options.Scope.Add("{SCOPE}");, dove il {SCOPE} segnaposto è l'ambito aggiuntivo da aggiungere.

Definire un servizio provider di token a scopo limitato che può essere utilizzato all'interno dell'app per risolvere i token dall'iniezione delle dipendenze (DI).

TokenProvider.cs:

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

Program Nel file aggiungere servizi per:

  • IHttpClientFactory: usato in una WeatherForecastService classe che ottiene i dati meteo da un'API server con un token di accesso.
  • TokenProvider: contiene i token di accesso e di aggiornamento.
builder.Services.AddHttpClient();
builder.Services.AddScoped<TokenProvider>();

In Startup.ConfigureServices, aggiungi servizi per Startup.cs:

  • IHttpClientFactory: usato in una WeatherForecastService classe che ottiene i dati meteo da un'API server con un token di accesso.
  • TokenProvider: contiene i token di accesso e di aggiornamento.
services.AddHttpClient();
services.AddScoped<TokenProvider>();

Definire una classe per trasmettere lo stato iniziale dell'applicazione con i token di accesso e di aggiornamento.

InitialApplicationState.cs:

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

Nel file Pages/_Host.cshtml, creare un'istanza di InitialApplicationState e passarla come parametro all'app:

Nel file Pages/_Layout.cshtml, creare un'istanza di InitialApplicationState e passarla come parametro all'app:

Nel file Pages/_Host.cshtml, creare un'istanza di InitialApplicationState e passarla come parametro all'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" ... />

App Nel componente (App.razor) risolvere il servizio e inizializzarlo con i dati del parametro :

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

Nota

Un'alternativa all'assegnazione dello stato iniziale a TokenProvider nell'esempio precedente consiste nel copiare i dati in un servizio con ambito all'interno di OnInitializedAsync per l'uso nell'app.

Aggiungere un riferimento al pacchetto nell'app per il pacchetto NuGet Microsoft.AspNet.WebApi.Client.

Nota

Per indicazioni sull'aggiunta di pacchetti alle app .NET, vedere gli articoli sotto Installare e gestire pacchetti in Flusso di lavoro dell'utilizzo di pacchetti (documentazione di NuGet). Confermare le versioni corrette del pacchetto all'indirizzo NuGet.org.

Nel servizio che effettua una richiesta API sicura inserire il provider di token e recuperare il token per la richiesta API:

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;
        using var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://localhost:5003/WeatherForecast");
        request.Headers.Add("Authorization", $"Bearer {token}");
        using var response = await http.SendAsync(request);
        response.EnsureSuccessStatusCode();

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

Per un token XSRF passato a un componente, inserire TokenProvider e aggiungere il token XSRF alla richiesta POST. L'esempio seguente aggiunge il token a un endpoint di disconnessione POST. Lo scenario per l'esempio seguente è che l'endpoint di disconnessione (Areas/Identity/Pages/Account/Logout.cshtml, inserito tramite scaffolding nell'app) non specifica un IgnoreAntiforgeryTokenAttribute (@attribute [IgnoreAntiforgeryToken]) perché esegue un'azione aggiuntiva rispetto a una normale operazione di disconnessione, che deve essere protetta. L'endpoint richiede un token XSRF valido per elaborare correttamente la richiesta.

In un componente che presenta un pulsante disconnessione per gli utenti autorizzati:

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

Impostare lo schema di autenticazione

Per un'app che usa più middleware di autenticazione e pertanto ha più schemi di autenticazione, lo schema usato Blazor può essere impostato in modo esplicito nella configurazione dell'endpoint del Program file. L'esempio seguente imposta lo schema OpenID Connect (OIDC):

Per un'app che usa più di un middleware di autenticazione e pertanto ha più schemi di autenticazione, lo schema che Blazor usa può essere impostato in modo esplicito nella configurazione dell'endpoint di Startup.cs. L'esempio seguente imposta lo schema OpenID Connect (OIDC):

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

Per un'app che usa più di un middleware di autenticazione e pertanto ha più schemi di autenticazione, lo schema che Blazor usa può essere impostato in modo esplicito nella configurazione dell'endpoint di Startup.Configure. Il seguente esempio imposta lo schema Microsoft Entra ID.

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

Utilizzare OpenID Connect (OIDC) v2.0 endpoint.

Nelle versioni di ASP.NET Core precedenti a .NET 5, la libreria di autenticazione e Blazor i modelli usano endpoint OpenID Connect (OIDC) v1.0. Per usare un endpoint v2.0 con versioni di ASP.NET Core precedenti a .NET 5, configurare l'opzione OpenIdConnectOptions.Authority in OpenIdConnectOptions:

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

In alternativa, l'impostazione può essere eseguita nel file delle impostazioni dell'app (appsettings.json):

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

Se l'aggiunta di un segmento all'autorità non è appropriata per il provider OIDC dell'app, ad esempio con provider non ME-ID, impostare direttamente la proprietà Authority. Impostare la proprietà in OpenIdConnectOptions o nel file di impostazioni dell'app con la Authority chiave .

Modifiche al codice

  • L'elenco delle attestazioni nel token ID cambia per gli endpoint v2.0. La documentazione Microsoft sulle modifiche è stata ritirata, ma le indicazioni sulle attestazioni in un token ID sono disponibili nel riferimento alle attestazioni del token ID.

  • Poiché le risorse sono specificate negli URI di ambito per gli endpoint v2.0, rimuovere l'impostazione della OpenIdConnectOptions.Resource proprietà in OpenIdConnectOptions:

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

URI ID App

  • Quando si usano endpoint v2.0, le API definiscono un App ID URI, destinato a rappresentare un identificatore univoco per l'API.
  • Tutti gli ambiti includono l'URI ID app come prefisso e gli endpoint v2.0 generano token di accesso con l'URI ID app come gruppo di destinatari.
  • Quando si usano gli endpoint V2.0, l'ID client configurato nell'API server passa dall'ID applicazione API (ID client) all'URI ID app.

appsettings.json:

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

È possibile trovare l'URI ID app da usare nella descrizione della registrazione dell'app del provider OIDC.

Gestore del circuito per acquisire gli utenti per i servizi personalizzati

Usare un CircuitHandler per acquisire un utente da AuthenticationStateProvider e configurare l'utente in un servizio. Se vuoi aggiornare l'utente, registra un callback in AuthenticationStateChanged ed accoda un Task per ottenere il nuovo utente e aggiornare il servizio. Nell'esempio seguente viene illustrato l'approccio .

Nell'esempio seguente :

  • OnConnectionUpAsync viene chiamato ogni volta che il circuito si riconnette, impostando l'utente per la durata della connessione. È necessario solo il OnConnectionUpAsync metodo, a meno che non si implementino gli aggiornamenti tramite un gestore per le modifiche di autenticazione (AuthenticationChanged nell'esempio seguente).
  • OnCircuitOpenedAsync viene chiamato per collegare il gestore di autenticazione modificato, AuthenticationChanged, per aggiornare l'utente.
  • Il catch blocco dell'attività UpdateAuthentication non esegue alcuna azione sulle eccezioni perché non è possibile segnalare le eccezioni a questo punto nell'esecuzione del codice. Se dall'attività viene generata un'eccezione, quest'ultima viene segnalata altrove nell'applicazione.

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

Nel file 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 di Startup.cs:

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

...

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

Usare il servizio in un componente per ottenere l'utente:

@inject UserService UserService

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

Per impostare l'utente nel middleware per MVC, Razor Pages e in altri scenari di ASP.NET Core, chiamare SetUser sul UserService nel middleware personalizzato dopo che il Middleware di Autenticazione è stato eseguito, o impostare l'utente con un'implementazione IClaimsTransformation. Nell'esempio seguente viene adottato l'approccio middleware.

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

Immediatamente prima della chiamata a app.MapRazorComponents<App>() nel file Program, chiamare il middleware:

Immediatamente prima della chiamata a app.MapBlazorHub() nel file Program, chiamare il middleware:

Immediatamente prima della chiamata a app.MapBlazorHub() in Startup.Configure di Startup.cs, chiamare il middleware:

app.UseMiddleware<UserServiceMiddleware>();

Accesso AuthenticationStateProvider nel middleware delle richieste in uscita

È possibile accedere a AuthenticationStateProvider da un DelegatingHandler creato con HttpClient nel middleware delle richieste in uscita utilizzando un IHttpClientFactory.

Nota

Per indicazioni generali sulla definizione dei gestori di delega per le richieste HTTP per le istanze HttpClient create usando IHttpClientFactory nelle app ASP.NET Core, vedere le sezioni seguenti di Effettuare richieste HTTP usando IHttpClientFactory in ASP.NET Core:

Nell'esempio seguente viene AuthenticationStateProvider usato per associare un'intestazione di nome utente personalizzata per gli utenti autenticati alle richieste in uscita.

Per prima cosa, implementare la classe CircuitServicesAccessor nella sezione seguente dell'articolo sull'inserimento delle dipendenze (DI) Blazor.

Blazor

Usare il CircuitServicesAccessor per accedere all'oggetto AuthenticationStateProvider nell'implementazione DelegatingHandler.

AuthenticationStateHandler.cs:

using Microsoft.AspNetCore.Components.Authorization;

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

        if (authStateProvider is null)
        {
            throw new Exception("AuthenticationStateProvider not available");
        }

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

Nel file Program, registrare AuthenticationStateHandler e aggiungere il gestore al IHttpClientFactory che crea istanze di HttpClient.

builder.Services.AddTransient<AuthenticationStateHandler>();

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