Compartilhar via


ASP.NET Cenários de segurança adicionais e Blazor Web App do lado do servidor principal

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão do .NET 10 deste artigo.

Aviso

Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, consulte a Política de Suporte do .NET e do .NET Core. Para a versão atual, consulte a versão do .NET 10 deste artigo.

Este artigo explica como configurar o Blazor do lado do servidor para cenários de segurança adicionais, incluindo como passar tokens a um aplicativo Blazor.

Observação

Os exemplos de código neste artigo adotam tipos de referência anuláveis (NRTs) e análise estática de estado nulo do compilador .NET, que são compatíveis com ASP.NET Core no .NET 6 ou posterior. Quando direcionado ao .NET 5 ou anterior, remova a designação de tipo nulo (?) dos tipos string?, TodoItem[]?, WeatherForecast[]?, e IEnumerable<GitHubBranch>? nos exemplos do artigo.

Passar tokens para um aplicativo Blazor do lado do servidor

Esta seção se aplica a Blazor Web Apps. Para Blazor Server, ver a versão .NET 7 desta seção do artigo.

Se você quiser apenas usar tokens de acesso para fazer chamadas à API da web de um Blazor Web Appcliente HTTP nomeado, consulte a seção Usar um manipulador de token para chamadas à API da web, que explica como usar uma DelegatingHandler implementação para anexar o token de acesso de um usuário a solicitações de saída. As diretrizes a seguir nesta seção são para desenvolvedores que precisam de tokens de acesso, tokens de atualização e outras propriedades de autenticação do lado do servidor para outras finalidades.

Para salvar tokens e outras propriedades de autenticação para uso do lado do servidor em Blazor Web Apps, é recomendável usar IHttpContextAccessor/HttpContext (IHttpContextAccessor, HttpContext). A leitura de tokens de HttpContext, inclusive como um parâmetro em cascata, usando IHttpContextAccessor, é suportada para obtenção de tokens durante a renderização interativa do servidor, caso os tokens sejam obtidos durante a renderização estática do lado do servidor (SSR estático) ou pré-renderização. No entanto, os tokens não serão atualizados se o usuário se autenticar depois que o circuito for estabelecido, já que os tokens HttpContext são capturados no início da SignalR conexão. Além disso, o uso de AsyncLocal<T> por IHttpContextAccessor significa que você deve ter cuidado para não perder o contexto de execução antes de ler o HttpContext. Para mais informações, consulte Blazordo ASP.NET Core .

Em uma classe de serviço, obtenha acesso aos membros do namespace Microsoft.AspNetCore.Authentication para tornar o método GetTokenAsync acessível em HttpContext. Uma abordagem alternativa, que está comentada no exemplo a seguir, é chamar AuthenticateAsync em HttpContext. Para o retorno AuthenticateResult.Properties, chame 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;
    }
}

O serviço é registrado no arquivo de projeto de servidor Program.

builder.Services.AddScoped<AuthenticationProcessor>();

AuthenticationProcessor pode ser injetado em serviços do lado do servidor, por exemplo, em um DelegatingHandler para um preconfigurado HttpClient. O exemplo a seguir é apenas para fins de demonstração ou caso você precise executar um AuthenticationProcessor processamento especial no serviço, pois você pode apenas injetar IHttpContextAccessor e obter o token diretamente para chamar APIs Web externas (para obter mais informações sobre como usar IHttpContextAccessor diretamente para chamar APIs Web, consulte a seção Usar um manipulador de token para chamadas à 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);
    }
}

O manipulador de token é registrado e atua como o manipulador de delegação para um cliente HTTP nomeado no Program arquivo:

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

Cuidado

Verifique se os tokens nunca são transmitidos e manipulados pelo cliente (o .Client projeto), por exemplo, em um componente que adota a renderização automática interativa e é renderizado no cliente ou por um serviço do lado do cliente. Sempre faça com que o cliente chame o servidor (projeto) para processar solicitações com tokens. Tokens e outros dados de autenticação nunca devem sair do servidor.

Para componentes interativos do Auto, consulte ASP.NET Core autenticação e autorizaçãoBlazor, que demonstra como deixar tokens de acesso e outras propriedades de autenticação no servidor. Além disso, considere a adoção do padrão BFF (Backend-for-Frontend), que adota uma estrutura de chamadas semelhante e é descrito em Proteger um ASP.NET Core Blazor Web App com OpenID Connect (OIDC) para provedores OIDC e proteger um ASP.NET Core Blazor Web App com o Microsoft Entra ID para Microsoft Identity Web com o Entra.

Usar um manipulador de token para chamadas à API Web

A abordagem a seguir visa anexar o token de acesso de um usuário a solicitações de saída, especificamente para fazer chamadas à API Web para aplicativos de API Web externos. A abordagem é mostrada para um Blazor Web App que adota a renderização global do Interactive Server, mas a mesma abordagem geral se aplica a Blazor Web Apps que adotam o modo global de renderização automática interativa. O conceito importante a ter em mente é que acessar o HttpContext usando IHttpContextAccessor é realizado apenas no servidor.

Para obter uma demonstração das diretrizes nesta seção, consulte os BlazorWebAppOidc aplicativos de exemplo BlazorWebAppOidcServer (.NET 8 ou posterior) no Blazor repositório GitHub de exemplos. Os exemplos adotam um modo de renderização interativo global e autenticação OIDC com o Microsoft Entra sem usar pacotes específicos do Entra. Os exemplos demonstram como passar um token de acesso JWT para chamar uma API Web segura.

A plataforma de identidade da Microsoft com pacotes Web da Microsoft Identity para Microsoft Entra ID fornece uma API para chamar APIs web a partir de Blazor Web App com gerenciamento automático e renovação de token. Para obter mais informações, consulte Proteja um ASP.NET Core Blazor Web App com Microsoft Entra ID e os aplicativos de exemplo BlazorWebAppEntra e BlazorWebAppEntraBff (.NET 9 ou posterior) no repositório do GitHub de exemplos Blazor.

Subclasse DelegatingHandler para anexar o token de acesso de um usuário a solicitações de saída. O manipulador de token só é executado no servidor, portanto, o uso HttpContext é seguro.

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

Observação

Para obter diretrizes sobre como acessar um AuthenticationStateProvider de um DelegatingHandler, consulte a seção Acessar AuthenticationStateProvider no middleware de solicitações de saída.

No arquivo do projeto Program, o manipulador de token (TokenHandler) é registrado como um serviço escopado e especificado como um manipulador de mensagens de cliente HTTP com nome definido com AddHttpMessageHandler.

No exemplo a seguir, o marcador {HTTP CLIENT NAME} é o nome do HttpClient, e o marcador {BASE ADDRESS} é o URI de base de endereço da API Web. Para obter mais informações sobreAddHttpContextAccessor, consulte IHttpContextAccessor/HttpContext em aplicativos do ASP.NET CoreBlazor.

Em Program.cs:

builder.Services.AddHttpContextAccessor();

builder.Services.AddScoped<TokenHandler>();

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

Exemplo:

builder.Services.AddScoped<TokenHandler>();

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

Você pode fornecer o endereço base do cliente HTTP através da configuração, onde builder.Configuration["{CONFIGURATION KEY}"], sendo {CONFIGURATION KEY}, é a chave de configuração:

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

Em appsettings.json, especifique ExternalApiUri. O exemplo a seguir define o valor para o endereço localhost da API web externa em https://localhost:7277:

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

Neste ponto, um HttpClient criado por um componente pode fazer solicitações de API Web seguras. No exemplo a seguir, o {REQUEST URI} é o URI de solicitação relativa, e o {HTTP CLIENT NAME} é o espaço reservado para o nome do HttpClient:

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

Exemplo:

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

Recursos adicionais estão planejados para Blazor, que são rastreados pelo Access AuthenticationStateProvider no middleware de solicitação de saída (dotnet/aspnetcore nº 52379). Problema ao fornecer o Token de Acesso ao HttpClient no modo servidor interativo (dotnet/aspnetcore nº 52390) é um problema fechado que contém estratégias úteis de discussão e possíveis soluções alternativas para casos de uso avançado.

Os tokens disponíveis fora dos componentes Razor em um aplicativo Blazor do lado do servidor podem ser passados aos componentes com a abordagem descrita nesta seção. O exemplo nesta seção se concentra na passagem de tokens de acesso, de atualização e token de prevenção de solicitação de falsificação (XSRF) do aplicativo Blazor, mas a abordagem é válida para outro estado de contexto HTTP.

Observação

Passar o token XSRF para componentes Razor é útil em cenários onde os componentes POST para Identity ou outros pontos de extremidade exigem validação. Se seu aplicativo exigir apenas tokens de acesso e de atualização, você poderá remover o código do token XSRF do exemplo a seguir.

Autentique o aplicativo como faria com um aplicativo Razor Pages ou MVC comum. Provisione e salve os tokens na autenticação cookie.

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

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

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

Opcionalmente, escopos adicionais são adicionados com options.Scope.Add("{SCOPE}");, em que o espaço reservado {SCOPE} é o escopo adicional a ser adicionado.

Defina um serviço de provedor de token com escopo que pode ser usado dentro do Blazor aplicativo para resolver os tokens da DI (injeção de dependência).

TokenProvider.cs:

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

No arquivo Program, adicione serviços para:

  • IHttpClientFactory: usado em uma classe WeatherForecastService que obtém dados meteorológicos de uma API de servidor com um token de acesso.
  • TokenProvider: mantém os tokens de acesso e atualização.
builder.Services.AddHttpClient();
builder.Services.AddScoped<TokenProvider>();

Em Startup.ConfigureServices de Startup.cs, adicione serviços para:

  • IHttpClientFactory: usado em uma classe WeatherForecastService que obtém dados meteorológicos de uma API de servidor com um token de acesso.
  • TokenProvider: mantém os tokens de acesso e atualização.
services.AddHttpClient();
services.AddScoped<TokenProvider>();

Defina uma classe para passar o estado inicial do aplicativo com os tokens de acesso e atualização.

InitialApplicationState.cs:

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

No arquivo Pages/_Host.cshtml, crie e a instância de InitialApplicationStatee passe-o como um parâmetro para o aplicativo:

No arquivo Pages/_Layout.cshtml, crie e a instância de InitialApplicationStatee passe-o como um parâmetro para o aplicativo:

No arquivo Pages/_Host.cshtml, crie e a instância de InitialApplicationStatee passe-o como um parâmetro para o aplicativo:

@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" ... />

No componente App (App.razor), resolva o serviço e inicialize-o com os dados do parâmetro:

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

Observação

Uma alternativa para atribuir o estado inicial ao TokenProvider no exemplo anterior é copiar os dados para um serviço com escopo no OnInitializedAsync para uso em todo o aplicativo.

Adicione uma referência de pacote ao aplicativo para o pacote NuGet Microsoft.AspNet.WebApi.Client.

Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de pacotes (documentação do NuGet). Confirme as versões corretas de pacote em NuGet.org.

No serviço que faz uma solicitação de API segura, injete o provedor de token e recupere o token para a solicitação de 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>();
    }
}

Para um token XSRF passado para um componente, injete o TokenProvider e adicione o token XSRF à solicitação POST. O exemplo a seguir adiciona o token a um ponto de extremidade de logoff POST. O cenário do exemplo a seguir é que o ponto de extremidade de logoff (Areas/Identity/Pages/Account/Logout.cshtml, scaffolded no aplicativo) não especifica um IgnoreAntiforgeryTokenAttribute (@attribute [IgnoreAntiforgeryToken]) porque ele executa alguma ação além de uma operação de logoff normal que deve ser protegida. O ponto de extremidade requer um token XSRF válido para processar a solicitação com êxito.

Em um componente que apresenta um botão Logoff para usuários autorizados:

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

Definir o esquema de autenticação

Para um aplicativo que usa mais de um Middleware de Autenticação e, portanto, tem mais de um esquema de autenticação, o esquema que o Blazor usa pode ser explicitamente definido na configuração do ponto de extremidade do arquivo Program. O exemplo a seguir define o esquema OIDC (OpenID Connect):

Para um aplicativo que usa mais de um Middleware de Autenticação e, portanto, tem mais de um esquema de autenticação, o esquema que Blazor usa pode ser definido explicitamente na configuração do ponto de extremidade do Startup.cs. O exemplo a seguir define o esquema OIDC (OpenID Connect):

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

Para um aplicativo que usa mais de um Middleware de Autenticação e, portanto, tem mais de um esquema de autenticação, o esquema que Blazor usa pode ser definido explicitamente na configuração do ponto de extremidade do Startup.Configure. O exemplo a seguir define o esquema do Microsoft Entra ID:

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

Usar pontos de extremidade do OIDC (OpenID Connect) v2.0

Em versões do ASP.NET Core anteriores ao .NET 5, a biblioteca de autenticação e os modelos Blazor usam os endpoints do OpenID Connect (OIDC) v1.0. Para usar um endpoint v2.0 com versões do ASP.NET Core anteriores ao .NET 5, configure a opção OpenIdConnectOptions.Authority no OpenIdConnectOptions:

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

Como alternativa, a configuração pode ser feita no arquivo de configurações do aplicativo (appsettings.json):

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

Se anexar um segmento à autoridade não for apropriado para o provedor OIDC do aplicativo, como acontece com provedores que não são ME-ID, defina a propriedade Authority diretamente. Defina a propriedade em OpenIdConnectOptions ou no arquivo de configurações do aplicativo com a chave Authority.

Alterações de código

  • A lista de declarações no token de ID muda para pontos de extremidade v2.0. A documentação da Microsoft sobre as alterações foi desativada, mas as diretrizes sobre as declarações em um token de ID estão disponíveis na referência de declarações de token de ID.

  • Como os recursos são especificados em URIs de escopo para pontos de extremidade v2.0, remova a configuração da propriedade do OpenIdConnectOptions.Resource em OpenIdConnectOptions:

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

URI da ID do aplicativo

  • Ao usar pontos de extremidade v2.0, as APIs definem um App ID URI, que deve representar um identificador exclusivo para a API.
  • Todos os escopos incluem o URI da ID do Aplicativo como prefixo e os pontos de extremidade v2.0 emitem tokens de acesso com o URI da ID do Aplicativo como público-alvo.
  • Ao usar pontos de extremidade V2.0, a ID do cliente configurada na API do Servidor muda da ID do Aplicativo de API (ID do Cliente) para o URI da ID do Aplicativo.

appsettings.json:

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

Você pode encontrar o URI da ID do Aplicativo a ser usado na descrição do registro do aplicativo do provedor OIDC.

Manipulador de circuito para capturar usuários a serviços personalizados

Use um CircuitHandler para capturar um usuário do AuthenticationStateProvider e definir esse usuário em um serviço. Se você quiser atualizar o usuário, registre um retorno de chamada para AuthenticationStateChanged e enfileire um Task para obter o novo usuário e atualize o serviço. O exemplo a seguir demonstra a abordagem.

No exemplo a seguir:

  • OnConnectionUpAsync é chamado sempre que o circuito se reconecta, definindo o usuário para o tempo de vida da conexão. Somente o método OnConnectionUpAsync é necessário, a menos que você implemente atualizações por meio de um manipulador para alterações de autenticação (AuthenticationChanged no exemplo a seguir).
  • OnCircuitOpenedAsync é chamado para anexar o manipulador alterado de autenticação,AuthenticationChanged, para atualizar o usuário.
  • O bloco catch da tarefa UpdateAuthentication não executa nenhuma ação nas exceções porque não há como informá-las neste momento da execução do código. Se uma exceção for gerada da tarefa, a exceção será relatada em outro lugar no aplicativo.

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

No arquivo Program:

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

...

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

No Startup.ConfigureServices do Startup.cs:

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

...

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

Use o serviço em um componente para obter o usuário:

@inject UserService UserService

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

Para definir o usuário no middleware para MVC, Razor Pages e em outros cenários ASP.NET Core, chame SetUser no UserService middleware personalizado após a execução do Middleware de Autenticação ou defina o usuário com uma implementação IClaimsTransformation. O exemplo a seguir adota a abordagem de 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);
    }
}

Imediatamente antes da chamada para app.MapRazorComponents<App>() no arquivo Program, chame o middleware:

Imediatamente antes da chamada para app.MapBlazorHub() no arquivo Program, chame o middleware:

Imediatamente antes da chamada para app.MapBlazorHub() no Startup.Configure do Startup.cs, chame o middleware:

app.UseMiddleware<UserServiceMiddleware>();

Acessar AuthenticationStateProvider no middleware de solicitação de saída

O AuthenticationStateProvider de um DelegatingHandler para HttpClient criado com IHttpClientFactory pode ser acessado no middleware de solicitação de saída usando um manipulador de atividade de circuito.

Observação

Para obter diretrizes gerais sobre como definir manipuladores de delegação para solicitações HTTP por instâncias HttpClient criadas usando IHttpClientFactory em aplicativos ASP.NET Core, confira as seguintes seções Fazer solicitações HTTP usando IHttpClientFactory no ASP.NET Core:

O exemplo a seguir usa AuthenticationStateProvider para anexar um cabeçalho de nome de usuário personalizado de usuários autenticados a solicitações de saída.

Primeiro, implemente a classe CircuitServicesAccessor na seção a seguir do artigo de injeção de dependência (DI) Blazor:

Acessar serviços Blazor do lado do servidor de um escopo de DI diferente

Use o CircuitServicesAccessor para acessar o AuthenticationStateProvider na implementação de 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);
    }
}

No arquivo Program, registre o AuthenticationStateHandler e adicione o manipulador ao IHttpClientFactory que cria instâncias HttpClient:

builder.Services.AddTransient<AuthenticationStateHandler>();

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