Partilhar via


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

Observação

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

Advertência

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

Importante

Estas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

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

Observação

Os exemplos de código neste artigo adotam tipos de referência anuláveis (NRTs) ede análise estática de estado nulo do compilador .NET, que são suportados no ASP.NET Core no .NET 6 ou posterior. Ao direcionar ASP.NET Core 5.0 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

Para Blazor Web App, as orientações sobre como passar tokens para Razor componentes devem ser abordadas na documentação (dotnet/AspNetCore.Docs #31691) em 2025 para uma versão prévia do .NET 10.

Para obter mais informações, consulte as seguintes questões:

Para Blazor Server, consulte a versão 7.0 deste artigo na seção .

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

Observação

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

Autentique o aplicativo como faria com um aplicativo Razor Pages ou MVC normal. Forneça e guarde os tokens na autenticação cookie.

No ficheiro 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 acrescentados com options.Scope.Add("{SCOPE}");, sendo o marcador {SCOPE} o escopo adicional a ser adicionado.

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

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 no 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 uma instância de InitialApplicationState e passe-a como um parâmetro para o aplicativo:

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

No arquivo Pages/_Host.cshtml, crie uma instância de InitialApplicationState e passe-a 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 em um serviço com escopo dentro do OnInitializedAsync para uso em todo o aplicativo.

Adicione uma referência de pacote à aplicação para o pacote NuGet Microsoft.AspNet.WebApi.Client.

Observação

Para obter orientação sobre como adicionar pacotes a aplicações .NET, consulte os artigos na secção Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de pacotes (documentação do NuGet). Confirme as versões corretas do 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;
        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>();
    }
}

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 POST de ponto de extremidade de logout. O cenário para o exemplo a seguir é que o ponto de extremidade de logout (Areas/Identity/Pages/Account/Logout.cshtml, integrado na aplicação) não especifica uma IgnoreAntiforgeryTokenAttribute (@attribute [IgnoreAntiforgeryToken]) porque executa uma ação adicional a uma operação de logout 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 Logout 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 Blazor usa pode ser explicitamente definido na configuração de ponto de extremidade do arquivo Program. O exemplo a seguir define o esquema OpenID Connect (OIDC):

Para uma aplicação que utiliza mais de um middleware de autenticação e, portanto, tem mais de um esquema de autenticação, o esquema que Blazor usa pode ser explicitamente definido na configuração do ponto final de Startup.cs. O exemplo a seguir define o esquema 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
    });

Para uma aplicação 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 explicitamente definido na configuração do endpoint de Startup.Configure. O exemplo a seguir define o esquema de ID do Microsoft Entra:

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

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

Em versões do ASP.NET Core anteriores à 5.0, a biblioteca de autenticação e os modelos Blazor utilizam endpoints OpenID Connect (OIDC) v1.0. Para usar um endpoint v2.0 com versões do ASP.NET Core anteriores a 5.0, 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 adicionar um segmento à autoridade não for apropriado para o provedor OIDC da aplicação, como acontece com provedores que não sãoME-ID, configure a propriedade Authority diretamente. Defina a propriedade no 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 nos endpoints da versão 2.0. A documentação da Microsoft sobre as alterações foi desativada, mas a orientação sobre as reivindicações em um token de ID está disponível na referência de reivindicações de token de ID .

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

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

URI da ID do aplicativo

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

appsettings.json:

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

Você pode encontrar o URI da ID da aplicação para usar na descrição do registo do aplicativo do fornecedor OIDC.

Manipulador de circuitos para capturar usuários para serviços personalizados

Use um CircuitHandler para capturar um usuário do AuthenticationStateProvider e definir o 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 atualizar o serviço. O exemplo a seguir demonstra a abordagem.

No exemplo a seguir:

  • OnConnectionUpAsync é chamado toda vez 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 a anexar o manipulador de alteração de autenticação AuthenticationChangedpara atualizar o usuário.
  • O bloco catch da tarefa UpdateAuthentication não executa nenhuma ação sobre exceções porque não há como relatar as exceções neste ponto da execução do código. Se uma exceção for lançada da tarefa, a exceção será comunicada em outro lugar na aplicação.

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 ficheiro Program:

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

...

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

Em Startup.ConfigureServices de 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 num/a componente para obter o utilizador:

@inject UserService UserService

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

Para definir o utilizador no middleware para MVC, Razor Pages e em outros cenários ASP.NET Core, chame SetUser no UserService do middleware personalizado após a execução do Middleware de Autenticação ou defina o utilizador 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() em Startup.Configure de Startup.cs, chame o middleware:

app.UseMiddleware<UserServiceMiddleware>();

Acesso 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 orientação geral sobre como definir manipuladores de delegação para solicitações HTTP por instâncias HttpClient criadas usando IHttpClientFactory em aplicativos do ASP.NET Core, consulte as seguintes seções de 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 para usuários autenticados a solicitações de saída.

Primeiro, implemente a classe CircuitServicesAccessor na seguinte secção do artigo sobre injeção de dependência (DI) de Blazor.

Aceder a serviços do lado do servidor Blazor a partir de um escopo de DI diferente

Use o CircuitServicesAccessor para acessar o AuthenticationStateProvider na implementação DelegatingHandler.

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

No ficheiro Program, registe o AuthenticationStateHandler e adicione o handler ao IHttpClientFactory que cria instâncias do HttpClient.

builder.Services.AddTransient<AuthenticationStateHandler>();

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