Partilhar via


ASP.NET Core Blazor WebAssembly cenários adicionais de segurança

Note

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

Warning

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.

Este artigo descreve cenários de segurança adicionais para Blazor WebAssembly aplicativos.

Anexar tokens a solicitações de saída

AuthorizationMessageHandler é um DelegatingHandler usado para processar tokens de acesso. Os tokens são adquiridos usando o IAccessTokenProvider serviço, que é registrado pela estrutura. Se um token não puder ser adquirido, um AccessTokenNotAvailableException será lançado. AccessTokenNotAvailableException tem um método Redirect que navega para AccessTokenResult.InteractiveRequestUrl usando o AccessTokenResult.InteractionOptions fornecido para permitir a atualização do token de acesso.

Por conveniência, o framework fornece o BaseAddressAuthorizationMessageHandler pré-configurado com o endereço base da aplicação como uma URL autorizada. Os tokens de acesso só são adicionados quando o URI da solicitação está dentro do URI base do aplicativo. Quando os URIs de solicitação de saída não estiverem dentro do URI base do aplicativo, use uma classe personalizada AuthorizationMessageHandler (recomendado) ou configure o AuthorizationMessageHandler.

Note

Além da configuração do aplicativo cliente para acesso à API do servidor, a API do servidor também deve permitir solicitações de origem cruzada (CORS) quando o cliente e o servidor não residem no mesmo endereço base. Para obter mais informações sobre a configuração do CORS do lado do servidor, consulte a seção Cross-Origin Resource Sharing (CORS) mais adiante neste artigo.

No exemplo a seguir:

No exemplo a seguir, HttpClientFactoryServiceCollectionExtensions.AddHttpClient é uma extensão em Microsoft.Extensions.Http. Adicione o pacote a um aplicativo que ainda não faz referência a ele.

Note

Para obter orientação sobre como adicionar pacotes a aplicativos .NET, consulte os artigos na seção Instalar e gerenciar pacotes em Workflow de utilização de pacotes (documentação do NuGet). Confirme as versões corretas do pacote em NuGet.org.

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient("WebAPI", 
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("WebAPI"));

Para uma Blazorsolução hospedada com base no Blazor WebAssembly modelo de projeto, os URIs de solicitação estão dentro do URI base da aplicação. Portanto, IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) é atribuído ao HttpClient.BaseAddress em um aplicativo gerado a partir do modelo de projeto.

O HttpClient configurado é usado para fazer solicitações autorizadas seguindo o padrão try-catch.

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http

...

protected override async Task OnInitializedAsync()
{
    try
    {
        var examples = 
            await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

        ...
    }
    catch (AccessTokenNotAvailableException exception)
    {
        exception.Redirect();
    }
}

Cenários de solicitação de autenticação personalizados

Os cenários a seguir demonstram como personalizar solicitações de autenticação e como obter o caminho de login a partir das opções de autenticação.

Personalizar o processo de login

Gerencie parâmetros adicionais para uma solicitação de login com os seguintes métodos uma ou mais vezes em uma nova instância de InteractiveRequestOptions:

No exemplo de componente a seguir LoginDisplay , parâmetros adicionais são adicionados à solicitação de login:

  • prompt está definido como login: Força o usuário a inserir suas credenciais nessa solicitação, negando o logon único.
  • loginHint está definido como peter@contoso.com: Pré-preenche o campo de nome de utilizador/endereço de e-mail da página de início de sessão para o utilizador para peter@contoso.com. Os aplicativos geralmente usam esse parâmetro durante a reautenticação, já tendo extraído o nome de usuário de um login anterior usando a preferred_username declaração.

Shared/LoginDisplay.razor:

@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity?.Name!
        <button @onclick="BeginLogOut">Log out</button>
    </Authorized>
    <NotAuthorized>
        <button @onclick="BeginLogIn">Log in</button>
    </NotAuthorized>
</AuthorizeView>

@code{
    public void BeginLogOut()
    {
        Navigation.NavigateToLogout("authentication/logout");
    }

    public void BeginLogIn()
    {
        InteractiveRequestOptions requestOptions =
            new()
            {
                Interaction = InteractionType.SignIn,
                ReturnUrl = Navigation.Uri,
            };

        requestOptions.TryAddAdditionalParameter("prompt", "login");
        requestOptions.TryAddAdditionalParameter("loginHint", "peter@contoso.com");

        Navigation.NavigateToLogin("authentication/login", requestOptions);
    }
}

Para obter mais informações, consulte os seguintes recursos:

Personalize as opções antes de obter um token interativamente

Se ocorrer AccessTokenNotAvailableException , gerencie parâmetros adicionais para uma nova solicitação de token de acesso do provedor de identidade com os seguintes métodos uma ou mais vezes em uma nova instância de InteractiveRequestOptions:

No exemplo a seguir que obtém dados JSON via API da Web, parâmetros adicionais são adicionados à solicitação de redirecionamento se um token de acesso não estiver disponível (AccessTokenNotAvailableException for lançado):

  • prompt está definido como login: Força o usuário a inserir suas credenciais nessa solicitação, negando o logon único.
  • loginHint está definido como peter@contoso.com: Pré-preenche o campo de nome de utilizador/endereço de e-mail da página de início de sessão para o utilizador para peter@contoso.com. Os aplicativos geralmente usam esse parâmetro durante a reautenticação, já tendo extraído o nome de usuário de um login anterior usando a preferred_username declaração.
try
{
    var examples = await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

    ...
}
catch (AccessTokenNotAvailableException ex)
{
    ex.Redirect(requestOptions => {
        requestOptions.TryAddAdditionalParameter("prompt", "login");
        requestOptions.TryAddAdditionalParameter("loginHint", "peter@contoso.com");
    });
}

O exemplo anterior pressupõe que:

Para obter mais informações, consulte os seguintes recursos:

Personalizar opções ao usar um IAccessTokenProvider

Se a obtenção de um token falhar ao usar um IAccessTokenProvider, gerencie parâmetros adicionais para uma nova solicitação de token de acesso do provedor de identidade com os seguintes métodos uma ou mais vezes em uma nova instância de InteractiveRequestOptions:

No exemplo a seguir que tenta obter um token de acesso para o usuário, parâmetros adicionais são adicionados à solicitação de login se a tentativa de obter um token falhar quando TryGetToken for chamada:

  • prompt está definido como login: Força o usuário a inserir suas credenciais nessa solicitação, negando o logon único.
  • loginHint está definido como peter@contoso.com: Pré-preenche o campo de nome de utilizador/endereço de e-mail da página de início de sessão para o utilizador para peter@contoso.com. Os aplicativos geralmente usam esse parâmetro durante a reautenticação, já tendo extraído o nome de usuário de um login anterior usando a preferred_username declaração.
var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = [ ... ]
    });

if (!tokenResult.TryGetToken(out var token))
{
    tokenResult.InteractionOptions.TryAddAdditionalParameter("prompt", "login");
    tokenResult.InteractionOptions.TryAddAdditionalParameter("loginHint", 
        "peter@contoso.com");

    Navigation.NavigateToLogin(accessTokenResult.InteractiveRequestUrl, 
        accessTokenResult.InteractionOptions);
}
var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = new[] { ... }
    });

if (!tokenResult.TryGetToken(out var token))
{
    tokenResult.InteractionOptions.TryAddAdditionalParameter("prompt", "login");
    tokenResult.InteractionOptions.TryAddAdditionalParameter("loginHint", 
        "peter@contoso.com");

    Navigation.NavigateToLogin(accessTokenResult.InteractiveRequestUrl, 
        accessTokenResult.InteractionOptions);
}

O exemplo anterior pressupõe:

Para obter mais informações, consulte os seguintes recursos:

Sair com um endereço de retorno personalizado

O exemplo a seguir efetua logout do usuário e retorna o usuário ao /goodbye ponto de extremidade:

Navigation.NavigateToLogout("authentication/logout", "goodbye");

Obter o caminho de login a partir das opções de autenticação

Obtenha o caminho de login configurado em RemoteAuthenticationOptions:

var loginPath = 
    RemoteAuthOptions.Get(Options.DefaultName).AuthenticationPaths.LogInPath;

O exemplo anterior pressupõe:

Classe personalizada AuthorizationMessageHandler

Esta orientação nesta seção é recomendada para aplicativos cliente que fazem solicitações de saída para URIs que não estão dentro do URI base do aplicativo.

No exemplo a seguir, uma classe personalizada estende AuthorizationMessageHandler para uso como o DelegatingHandler para um HttpClient. ConfigureHandler configura esse manipulador para autorizar solicitações HTTP de saída usando um token de acesso. O token de acesso só será anexado se pelo menos uma das URLs autorizadas for uma base do URI da solicitação (HttpRequestMessage.RequestUri).

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, 
        NavigationManager navigation)
        : base(provider, navigation)
    {
        ConfigureHandler(
            authorizedUrls: [ "https://api.contoso.com/v1.0" ],
            scopes: [ "example.read", "example.write" ]);
    }
}

Note

Nesta seção, o manipulador de mensagens anterior é usado quando um HttpClient configurado é criado a partir de um IHttpClientFactory injetado. Se você não usar um IHttpClientFactory, deverá criar uma HttpClientHandler instância e atribuí-la ao AuthorizationMessageHandler:DelegatingHandler.InnerHandler

InnerHandler = new HttpClientHandler();

Você não precisa fazer a atribuição anterior InnerHandler se usar IHttpClientFactory, como a chamada ExampleAPIMethod demonstra mais adiante nesta seção.

No código anterior, os escopos e example.read são exemplos genéricos example.write não destinados a refletir escopos válidos para qualquer provedor específico.

No arquivo Program, CustomAuthorizationMessageHandler é registado como um serviço transitório e configurado como o DelegatingHandler para instâncias de HttpResponseMessage de saída feitas por um HttpClient nomeado.

No exemplo a seguir, HttpClientFactoryServiceCollectionExtensions.AddHttpClient é uma extensão em Microsoft.Extensions.Http. Adicione o pacote a um aplicativo que ainda não faz referência a ele.

Note

Para obter orientação sobre como adicionar pacotes a aplicativos .NET, consulte os artigos na seção Instalar e gerenciar pacotes em Workflow de utilização de pacotes (documentação do NuGet). Confirme as versões corretas do pacote em NuGet.org.

builder.Services.AddTransient<CustomAuthorizationMessageHandler>();

builder.Services.AddHttpClient("WebAPI",
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

Note

No exemplo anterior, o CustomAuthorizationMessageHandlerDelegatingHandler é registrado como um serviço transitório para AddHttpMessageHandler. O registo transitório é recomendado para IHttpClientFactory, que gere os seus próprios escopos de DI. Para obter mais informações, consulte os seguintes recursos:

Para uma solução Blazor hospedada com base no modelo de projeto Blazor WebAssembly, IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) é atribuído ao HttpClient.BaseAddress.

As configurações de HttpClient são usadas para fazer solicitações autorizadas seguindo o padrão try-catch. Quando o cliente é criado com CreateClient (Microsoft.Extensions.Http pacote), às HttpClient são fornecidas instâncias com tokens de acesso ao fazer solicitações à API do servidor. Se o URI da solicitação for um URI relativo, como no exemplo a seguir (ExampleAPIMethod), ele será combinado com o BaseAddress quando o aplicativo cliente fizer a solicitação:

@inject IHttpClientFactory ClientFactory

...

@code {
    protected override async Task OnInitializedAsync()
    {
        try
        {
            var client = ClientFactory.CreateClient("WebAPI");

            var examples = 
                await client.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

            ...
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

Configurar AuthorizationMessageHandler

AuthorizationMessageHandler pode ser configurado com URLs autorizadas, escopos e uma URL de retorno usando o ConfigureHandler método. ConfigureHandler configura o manipulador para autorizar solicitações HTTP de saída usando um token de acesso. O token de acesso só será anexado se pelo menos uma das URLs autorizadas for uma base do URI da solicitação (HttpRequestMessage.RequestUri). Se o URI de solicitação for um URI relativo, ele será combinado com o BaseAddress.

No exemplo a seguir, AuthorizationMessageHandler configura um HttpClient no Program arquivo:

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddScoped(sp => new HttpClient(
    sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: [ "https://api.contoso.com/v1.0" ],
        scopes: [ "example.read", "example.write" ])
    .InnerHandler = new HttpClientHandler())
{
    BaseAddress = new Uri("https://api.contoso.com/v1.0")
});

No código anterior:

Para uma solução de hospedagem Blazor baseada no modelo de Blazor WebAssembly projeto, IWebAssemblyHostEnvironment.BaseAddress é atribuído ao seguinte:

Inserido HttpClient

Um cliente tipado pode ser definido para lidar com todas as preocupações relacionadas ao HTTP e à aquisição de tokens, numa só classe.

WeatherForecastClient.cs:

using System.Net.Http.Json;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {PACKAGE ID/ASSEMBLY NAME}.Data;

public class WeatherForecastClient(HttpClient http)
{
    private WeatherForecast[]? forecasts;

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        try
        {
            forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }

        return forecasts ?? Array.Empty<WeatherForecast>();
    }
}

No exemplo anterior, o WeatherForecast tipo é uma classe estática que contém dados de previsão do tempo. O {PACKAGE ID/ASSEMBLY NAME} marcador de posição é o ID do pacote do projeto (<PackageId> no ficheiro de projeto) para o nome de uma biblioteca ou de um assembly de uma aplicação (por exemplo, using static BlazorSample.Data;).

No exemplo a seguir, HttpClientFactoryServiceCollectionExtensions.AddHttpClient é uma extensão em Microsoft.Extensions.Http. Adicione o pacote a um aplicativo que ainda não faz referência a ele.

Note

Para obter orientação sobre como adicionar pacotes a aplicativos .NET, consulte os artigos na seção Instalar e gerenciar pacotes em Workflow de utilização de pacotes (documentação do NuGet). Confirme as versões corretas do pacote em NuGet.org.

No ficheiro Program:

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

Para uma solução Blazor hospedada com base no modelo de projeto Blazor WebAssembly, IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) é atribuído ao HttpClient.BaseAddress.

Em um componente que busca dados meteorológicos:

@inject WeatherForecastClient Client

...

protected override async Task OnInitializedAsync()
{
    forecasts = await Client.GetForecastAsync();
}

Configurar o HttpClient manipulador

O manipulador pode ser configurado com ConfigureHandler para solicitações HTTP de saída.

No exemplo a seguir, HttpClientFactoryServiceCollectionExtensions.AddHttpClient é uma extensão em Microsoft.Extensions.Http. Adicione o pacote a um aplicativo que ainda não faz referência a ele.

Note

Para obter orientação sobre como adicionar pacotes a aplicativos .NET, consulte os artigos na seção Instalar e gerenciar pacotes em Workflow de utilização de pacotes (documentação do NuGet). Confirme as versões corretas do pacote em NuGet.org.

No ficheiro Program:

builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: [ "https://api.contoso.com/v1.0" ],
        scopes: [ "example.read", "example.write" ]));
builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"))
    .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new[] { "https://api.contoso.com/v1.0" },
        scopes: new[] { "example.read", "example.write" }));

No código anterior, os escopos e example.read são exemplos genéricos example.write não destinados a refletir escopos válidos para qualquer provedor específico.

Para uma solução de hospedagem Blazor baseada no modelo de Blazor WebAssembly projeto, IWebAssemblyHostEnvironment.BaseAddress é atribuído ao seguinte:

Solicitações de API da Web não autenticadas ou não autorizadas em um aplicativo com um cliente padrão seguro

Um aplicativo que normalmente usa um padrão HttpClient seguro também pode fazer solicitações de API da Web não autenticadas ou não autorizadas configurando um arquivo HttpClient.

No exemplo a seguir, HttpClientFactoryServiceCollectionExtensions.AddHttpClient é uma extensão em Microsoft.Extensions.Http. Adicione o pacote a um aplicativo que ainda não faz referência a ele.

Note

Para obter orientação sobre como adicionar pacotes a aplicativos .NET, consulte os artigos na seção Instalar e gerenciar pacotes em Workflow de utilização de pacotes (documentação do NuGet). Confirme as versões corretas do pacote em NuGet.org.

No ficheiro Program:

builder.Services.AddHttpClient("WebAPI.NoAuthenticationClient", 
    client => client.BaseAddress = new Uri("https://api.contoso.com/v1.0"));

Para uma solução Blazor hospedada com base no modelo de projeto Blazor WebAssembly, IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) é atribuído ao HttpClient.BaseAddress.

O registro anterior é adicional ao registro padrão HttpClient seguro existente.

Um componente cria o HttpClient a partir do IHttpClientFactory (Microsoft.Extensions.Http pacote) para fazer solicitações não autenticadas ou não autorizadas:

@inject IHttpClientFactory ClientFactory

...

@code {
    protected override async Task OnInitializedAsync()
    {
        var client = ClientFactory.CreateClient("WebAPI.NoAuthenticationClient");

        var examples = await client.GetFromJsonAsync<ExampleType[]>(
            "ExampleNoAuthentication");

        ...
    }
}

Note

O controlador na API do servidor, ExampleNoAuthenticationController para o exemplo anterior, não está marcado com o [Authorize] atributo.

A decisão de usar um cliente seguro ou um cliente inseguro como instância padrão HttpClient cabe ao desenvolvedor. Uma maneira de tomar esta decisão é considerar o número de endpoints autenticados versus não autenticados que a app contacta. Se a maioria das solicitações do aplicativo for para proteger pontos de extremidade de API, use a instância autenticada HttpClient como padrão. Caso contrário, registre a instância não autenticada HttpClient como padrão.

Uma abordagem alternativa ao uso do IHttpClientFactory é criar um cliente tipado para acesso não autenticado a endpoints anónimos.

Solicitar tokens de acesso adicionais

Os tokens de acesso podem ser obtidos manualmente chamando IAccessTokenProvider.RequestAccessToken. No exemplo a seguir, um escopo adicional é exigido por um aplicativo para o padrão HttpClient. O exemplo da Biblioteca de Autenticação da Microsoft (MSAL) configura o escopo com MsalProviderOptions:

No ficheiro Program:

builder.Services.AddMsalAuthentication(options =>
{
    ...

    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 1}");
    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 2}");
}

Os {CUSTOM SCOPE 1} e {CUSTOM SCOPE 2} espaços reservados no exemplo anterior são escopos personalizados.

Note

AdditionalScopesToConsent não é capaz de provisionar permissões de usuário delegadas para o Microsoft Graph por meio da interface do usuário de consentimento do Microsoft Entra ID quando um usuário usa pela primeira vez um aplicativo registrado no Microsoft Azure. Para obter mais informações, consulte Utilizar a API Graph com ASP.NET Core Blazor WebAssembly.

O IAccessTokenProvider.RequestAccessToken método fornece uma sobrecarga que permite que um aplicativo provisione um token de acesso com um determinado conjunto de escopos.

Em um Razor componente:

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider

...

var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = [ "{CUSTOM SCOPE 1}", "{CUSTOM SCOPE 2}" ]
    });

if (tokenResult.TryGetToken(out var token))
{
    ...
}
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider

...

var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = new[] { "{CUSTOM SCOPE 1}", "{CUSTOM SCOPE 2}" }
    });

if (tokenResult.TryGetToken(out var token))
{
    ...
}

Os {CUSTOM SCOPE 1} e {CUSTOM SCOPE 2} espaços reservados no exemplo anterior são escopos personalizados.

AccessTokenResult.TryGetToken retorna:

  • true com o token para uso.
  • false se o token não for recuperado.

Compartilhamento de recursos entre origens (CORS)

Ao enviar credenciais (cookies/cabeçalhos de autorização) nas solicitações CORS, deve ser permitido na política CORS o cabeçalho Authorization.

A política a seguir inclui configuração para:

  • Origem do pedido (http://localhost:5000, https://localhost:5001).
  • Qualquer método (verbo).
  • Content-Type e Authorization cabeçalhos. Para permitir um cabeçalho personalizado (por exemplo, x-custom-header), liste o cabeçalho ao chamar WithHeaders.
  • Credenciais definidas pelo código JavaScript do lado do cliente (credentials propriedade definida como include).
app.UseCors(policy => 
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
        .AllowAnyMethod()
        .WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, 
            "x-custom-header")
        .AllowCredentials());

Uma solução alojada Blazor baseada no modelo de Blazor WebAssembly projeto usa o mesmo endereço base para as aplicações cliente e servidor. O aplicativo cliente HttpClient.BaseAddress está configurado para um URI de builder.HostEnvironment.BaseAddress. A configuração CORS não é necessária na configuração padrão de uma solução hospedada Blazor . Aplicativos cliente adicionais que não são hospedados pelo projeto de servidor e não compartilham o endereço base do aplicativo de servidor exigem configuração CORS no projeto de servidor.

Para obter mais informações, consulte Habilitar solicitações entre origens (CORS) no ASP.NET Core e o componente HTTP Request Tester (Components/HTTPRequestTester.razor) do aplicativo de exemplo.

Manipular erros de solicitação de token

Quando um aplicativo de página única (SPA) autentica um usuário usando o OpenID Connect (OIDC), o estado de autenticação é mantido localmente no SPA e no Identity Provedor (IP) na forma de uma sessão cookie definida como resultado do usuário fornecer suas credenciais.

Os tokens que o IP emite para o usuário normalmente são válidos por curtos períodos de tempo, cerca de uma hora normalmente, portanto, o aplicativo cliente deve buscar regularmente novos tokens. Caso contrário, o usuário seria desconectado depois que os tokens concedidos expirarem. Na maioria dos casos, os clientes OIDC são capazes de provisionar novos tokens sem exigir que o usuário se autentique novamente graças ao estado de autenticação ou "sessão" que é mantida dentro do IP.

Há alguns casos em que o cliente não pode obter um token sem a interação do usuário, por exemplo, quando, por algum motivo, o usuário efetua logout explicitamente do IP. Este cenário ocorre se um utilizador visitar https://login.microsoftonline.com e terminar sessão. Nesses cenários, o aplicativo não sabe imediatamente que o usuário efetuou logout. Qualquer token que o cliente possua pode não ser mais válido. Além disso, o cliente não pode provisionar um novo token sem a interação do usuário depois que o token atual expira.

Esses cenários não são específicos da autenticação baseada em token. Fazem parte da natureza das Zonas de Proteção Especial. Um SPA que usa cookies também não chama uma API de servidor se a autenticação cookie for removida.

Quando um aplicativo executa chamadas de API para recursos protegidos, você deve estar ciente do seguinte:

  • Para provisionar um novo token de acesso para chamar a API, o usuário pode ser obrigado a se autenticar novamente.
  • Mesmo que o cliente tenha um token que pareça ser válido, a chamada para o servidor pode falhar porque o token foi revogado pelo usuário.

Quando o aplicativo solicita um token, há dois resultados possíveis:

  • A solicitação é bem-sucedida e o aplicativo tem um token válido.
  • A solicitação falha e o aplicativo deve autenticar o usuário novamente para obter um novo token.

Quando uma solicitação de token falha, você precisa decidir se deseja salvar qualquer estado atual antes de executar um redirecionamento. Existem várias abordagens para armazenar o estado com níveis crescentes de complexidade:

  • Armazene o estado atual da página no armazenamento da sessão. Durante o OnInitializedAsync método do ciclo de vida (OnInitializedAsync), verifique se o estado pode ser restaurado antes de continuar.
  • Adicione um parâmetro de cadeia de consulta e use-o para sinalizar ao aplicativo que é necessário restaurar o estado salvo anteriormente.
  • Adicione um parâmetro de cadeia de caracteres de consulta com um identificador exclusivo para armazenar dados no armazenamento de sessão sem correr o risco de colisões com outros itens.

Salvar o estado do aplicativo antes de uma operação de autenticação com armazenamento de sessão

O exemplo a seguir mostra como:

  • Preserve o estado antes de redirecionar para a página de login.
  • Recupere o estado anterior após a autenticação usando um parâmetro de cadeia de caracteres de consulta.
...
@using System.Text.Json
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
@inject IJSRuntime JS
@inject NavigationManager Navigation

<EditForm Model="User" OnSubmit="OnSaveAsync">
    <label>
        First Name: 
        <InputText @bind-Value="User!.Name" />
    </label>
    <label>
        Last Name: 
        <InputText @bind-Value="User!.LastName" />
    </label>
    <button type="submit">Save User</button>
</EditForm>

@code {
    public Profile User { get; set; } = new Profile();

    protected override async Task OnInitializedAsync()
    {
        var currentQuery = new Uri(Navigation.Uri).Query;

        if (currentQuery.Contains("state=resumeSavingProfile"))
        {
            var user = await JS.InvokeAsync<string>("sessionStorage.getItem",
                "resumeSavingProfile");

            if (!string.IsNullOrEmpty(user))
            {
                User = JsonSerializer.Deserialize<Profile>(user);
            }
        }
    }

    public async Task OnSaveAsync()
    {
        var http = new HttpClient();
        http.BaseAddress = new Uri(Navigation.BaseUri);

        var resumeUri = Navigation.Uri + $"?state=resumeSavingProfile";

        var tokenResult = await TokenProvider.RequestAccessToken(
            new AccessTokenRequestOptions
            {
                ReturnUrl = resumeUri
            });

        if (tokenResult.TryGetToken(out var token))
        {
            http.DefaultRequestHeaders.Add("Authorization", 
                $"Bearer {token.Value}");
            await http.PostAsJsonAsync("Save", User);
        }
        else
        {
            await JS.InvokeVoidAsync("sessionStorage.setItem", 
                "resumeSavingProfile", JsonSerializer.Serialize(User));
            Navigation.NavigateTo(tokenResult.InteractiveRequestUrl);
        }
    }

    public class Profile
    {
        public string? FirstName { get; set; }
        public string? LastName { get; set; }
    }
}

Salvar o estado do aplicativo antes de uma operação de autenticação com armazenamento de sessão e um contêiner de estado

Durante uma operação de autenticação, há casos em que você deseja salvar o estado do aplicativo antes que o navegador seja redirecionado para o IP. Esse pode ser o caso quando você estiver usando um contêiner de estado e quiser restaurar o estado depois que a autenticação for bem-sucedida. Você pode usar um objeto de estado de autenticação personalizado para preservar o estado específico do aplicativo ou uma referência a ele e restaurar esse estado depois que a operação de autenticação for concluída com êxito. O exemplo a seguir demonstra a abordagem.

Uma classe de contêiner de estado é criada no aplicativo com propriedades para armazenar os valores de estado do aplicativo. No exemplo a seguir, o contêiner é usado para manter o valor do contador do componente do modelo de projeto padrãoBlazor (Counter).Counter.razor Os métodos para serializar e desserializar o contêiner são baseados em System.Text.Json.

using System.Text.Json;

public class StateContainer
{
    public int CounterValue { get; set; }

    public string GetStateForLocalStorage() => JsonSerializer.Serialize(this);

    public void SetStateFromLocalStorage(string locallyStoredState)
    {
        var deserializedState = 
            JsonSerializer.Deserialize<StateContainer>(locallyStoredState);

        CounterValue = deserializedState.CounterValue;
    }
}

O Counter componente usa o contêiner de estado para manter o currentCount valor fora do componente:

@page "/counter"
@inject StateContainer State

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    protected override void OnInitialized()
    {
        if (State.CounterValue > 0)
        {
            currentCount = State.CounterValue;
        }
    }

    private void IncrementCount()
    {
        currentCount++;
        State.CounterValue = currentCount;
    }
}

Crie um ApplicationAuthenticationState a partir de RemoteAuthenticationState. Forneça uma Id propriedade, que serve como um identificador para o estado armazenado localmente.

ApplicationAuthenticationState.cs:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class ApplicationAuthenticationState : RemoteAuthenticationState
{
    public string? Id { get; set; }
}

O Authentication componente (Authentication.razor) salva e restaura o estado da aplicação usando o armazenamento de sessão local com os métodos de serialização e de desserialização StateContainer: GetStateForLocalStorage eSetStateFromLocalStorage.

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IJSRuntime JS
@inject StateContainer State

<RemoteAuthenticatorViewCore Action="Action"
                             TAuthenticationState="ApplicationAuthenticationState"
                             AuthenticationState="AuthenticationState"
                             OnLogInSucceeded="RestoreState"
                             OnLogOutSucceeded="RestoreState" />

@code {
    [Parameter]
    public string? Action { get; set; }

    public ApplicationAuthenticationState AuthenticationState { get; set; } =
        new ApplicationAuthenticationState();

    protected override async Task OnInitializedAsync()
    {
        if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
            Action) ||
            RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
            Action))
        {
            AuthenticationState.Id = Guid.NewGuid().ToString();

            await JS.InvokeVoidAsync("sessionStorage.setItem",
                AuthenticationState.Id, State.GetStateForLocalStorage());
        }
    }

    private async Task RestoreState(ApplicationAuthenticationState state)
    {
        if (state.Id != null)
        {
            var locallyStoredState = await JS.InvokeAsync<string>(
                "sessionStorage.getItem", state.Id);

            if (locallyStoredState != null)
            {
                State.SetStateFromLocalStorage(locallyStoredState);
                await JS.InvokeVoidAsync("sessionStorage.removeItem", state.Id);
            }
        }
    }
}

Este exemplo usa o Microsoft Entra (ME-ID) para autenticação. No ficheiro Program:

  • O ApplicationAuthenticationState está configurado como do tipo Biblioteca de Autenticação Microsoft (MSAL) RemoteAuthenticationState.
  • O contêiner de estado é registrado no contêiner de serviço.
builder.Services.AddMsalAuthentication<ApplicationAuthenticationState>(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});

builder.Services.AddSingleton<StateContainer>();

Personalizar rotas de aplicativos

A Microsoft.AspNetCore.Components.WebAssembly.Authentication biblioteca usa as rotas mostradas na tabela a seguir para representar diferentes estados de autenticação.

Route Purpose
authentication/login Aciona uma operação de entrada.
authentication/login-callback Manipula o resultado de qualquer operação de entrada.
authentication/login-failed Exibe mensagens de erro quando a operação de entrada falha por algum motivo.
authentication/logout Aciona uma operação de saída.
authentication/logout-callback Manipula o resultado de uma operação de saída.
authentication/logout-failed Exibe mensagens de erro quando a operação de saída falha por algum motivo.
authentication/logged-out Indica que o usuário efetuou logout com êxito.
authentication/profile Aciona uma operação para editar o perfil de usuário.
authentication/register Aciona uma operação para registrar um novo usuário.

As rotas mostradas na tabela anterior são configuráveis via RemoteAuthenticationOptions<TRemoteAuthenticationProviderOptions>.AuthenticationPaths. Ao definir opções para fornecer rotas personalizadas, confirme se o aplicativo tem uma rota que lida com cada caminho.

No exemplo a seguir, todos os caminhos são prefixados com /security.

Authentication componente (Authentication.razor):

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code{
    [Parameter]
    public string? Action { get; set; }
}
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code{
    [Parameter]
    public string Action { get; set; }
}

No ficheiro Program:

builder.Services.AddApiAuthorization(options => { 
    options.AuthenticationPaths.LogInPath = "security/login";
    options.AuthenticationPaths.LogInCallbackPath = "security/login-callback";
    options.AuthenticationPaths.LogInFailedPath = "security/login-failed";
    options.AuthenticationPaths.LogOutPath = "security/logout";
    options.AuthenticationPaths.LogOutCallbackPath = "security/logout-callback";
    options.AuthenticationPaths.LogOutFailedPath = "security/logout-failed";
    options.AuthenticationPaths.LogOutSucceededPath = "security/logged-out";
    options.AuthenticationPaths.ProfilePath = "security/profile";
    options.AuthenticationPaths.RegisterPath = "security/register";
});

Se o requisito exigir caminhos completamente diferentes, defina as rotas conforme descrito anteriormente e renderize o RemoteAuthenticatorView com um parâmetro de ação explícito:

@page "/register"

<RemoteAuthenticatorView Action="RemoteAuthenticationActions.Register" />

Você tem permissão para dividir a interface do usuário em páginas diferentes se optar por fazê-lo.

Personalizar a interface do usuário de autenticação

RemoteAuthenticatorView inclui um conjunto padrão de fragmentos de interface do usuário para cada estado de autenticação. Cada estado pode ser personalizado passando numa customização RenderFragment. Para personalizar o texto exibido durante o processo de login inicial, pode alterar o RemoteAuthenticatorView seguinte.

Authentication componente (Authentication.razor):

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action">
    <LoggingIn>
        You are about to be redirected to https://login.microsoftonline.com.
    </LoggingIn>
</RemoteAuthenticatorView>

@code{
    [Parameter]
    public string? Action { get; set; }
}
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action">
    <LoggingIn>
        You are about to be redirected to https://login.microsoftonline.com.
    </LoggingIn>
</RemoteAuthenticatorView>

@code{
    [Parameter]
    public string Action { get; set; }
}

O RemoteAuthenticatorView tem um fragmento que pode ser usado por rota de autenticação mostrada na tabela a seguir.

Route Fragment
authentication/login <LoggingIn>
authentication/login-callback <CompletingLoggingIn>
authentication/login-failed <LogInFailed>
authentication/logout <LogOut>
authentication/logout-callback <CompletingLogOut>
authentication/logout-failed <LogOutFailed>
authentication/logged-out <LogOutSucceeded>
authentication/profile <UserProfile>
authentication/register <Registering>

Personalizar o usuário

Os usuários vinculados ao aplicativo podem ser personalizados.

Personalizar o usuário com uma declaração de carga útil

No exemplo a seguir, os usuários autenticados do aplicativo recebem uma declaração para cada um amr dos métodos de autenticação do usuário. A amr declaração identifica como o assunto do token foi autenticado nas declarações de carga útil da plataforma de identidade da Microsoft v1.0. O exemplo usa uma classe de conta de usuário personalizada com base em RemoteUserAccount.

Crie uma classe que estenda a RemoteUserAccount classe. O exemplo a seguir define a propriedade AuthenticationMethod para a matriz de valores da propriedade JSON amr do utilizador. AuthenticationMethod é preenchido automaticamente pela estrutura quando o usuário é autenticado.

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomUserAccount : RemoteUserAccount
{
    [JsonPropertyName("amr")]
    public string[]? AuthenticationMethod { get; set; }
}

Crie uma fábrica que amplie AccountClaimsPrincipalFactory<TAccount> para criar declarações a partir dos métodos de autenticação do utilizador armazenados em CustomUserAccount.AuthenticationMethod:

using System.Security.Claims;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory(NavigationManager navigation,
    IAccessTokenProviderAccessor accessor)
    : AccountClaimsPrincipalFactory<CustomUserAccount>(accessor)
{
    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        CustomUserAccount account, RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity != null && initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = (ClaimsIdentity)initialUser.Identity;

            if (account.AuthenticationMethod is not null)
            {
                foreach (var value in account.AuthenticationMethod)
                {
                    userIdentity.AddClaim(new Claim("amr", value));
                }
            }
        }

        return initialUser;
    }
}

Registre o CustomAccountFactory para o provedor de autenticação em uso. Qualquer um dos seguintes registos é válido:

  • AddOidcAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddOidcAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddMsalAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddMsalAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddApiAuthorization:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddApiAuthorization<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    

ME-ID grupos e funções de segurança com uma classe de conta de usuário personalizada

Para obter um exemplo adicional que funciona com grupos de segurança ME-ID e ME-ID Funções de Administrador e uma classe de conta de usuário personalizada, consulte ASP.NET Core Blazor WebAssembly com grupos e funções do Microsoft Entra ID.

Pré-renderização com autenticação

Atualmente, não há suporte para pré-renderização de conteúdo que requer autenticação e autorização. Depois de seguir as orientações em um dos tópicos do aplicativo de Blazor WebAssembly segurança, use as instruções a seguir para criar um aplicativo que:

  • Pré-renderiza caminhos para os quais a autorização não é necessária.
  • Não pré-renderiza caminhos para os quais a autorização é necessária.

Para o arquivo do projeto Client, presente registros de serviço comuns em um método separado (por exemplo, criar um método no projeto Program). Serviços comuns são aqueles que o desenvolvedor registra para uso pelos projetos cliente e servidor.

public static void ConfigureCommonServices(IServiceCollection services)
{
    services.Add...;
}

No ficheiro Program:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
...

builder.Services.AddScoped( ... );

ConfigureCommonServices(builder.Services);

await builder.Build().RunAsync();

Server No ficheiro do projeto Program, registe os seguintes serviços adicionais e chame ConfigureCommonServices:

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddRazorPages();
builder.Services.TryAddScoped<AuthenticationStateProvider, 
    ServerAuthenticationStateProvider>();

Client.Program.ConfigureCommonServices(services);

Server No método do Startup.ConfigureServices projeto, registre os seguintes serviços adicionais e ligueConfigureCommonServices:

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddRazorPages();
    services.AddScoped<AuthenticationStateProvider, 
        ServerAuthenticationStateProvider>();
    services.AddScoped<SignOutSessionStateManager>();

    Client.Program.ConfigureCommonServices(services);
}

Para obter mais informações sobre o provedor de autenticação do servidor de framework (Blazor), consulte ServerAuthenticationStateProvider.

Server No arquivo do Pages/_Host.cshtml projeto, substitua o Component Tag Helper (<component ... />) pelo seguinte:

<div id="app">
    @if (HttpContext.Request.Path.StartsWithSegments("/authentication"))
    {
        <component type="typeof({CLIENT APP ASSEMBLY NAME}.App)" 
            render-mode="WebAssembly" />
    }
    else
    {
        <component type="typeof({CLIENT APP ASSEMBLY NAME}.App)" 
            render-mode="WebAssemblyPrerendered" />
    }
</div>

No exemplo anterior:

  • O {CLIENT APP ASSEMBLY NAME} espaço reservado é o nome do conjunto do aplicativo cliente (por exemplo, BlazorSample.Client).
  • A verificação condicional para o segmento de caminho /authentication:
    • Evita a renderização prévia (render-mode="WebAssembly") para caminhos de autenticação.
    • Prerenders (render-mode="WebAssemblyPrerendered") para caminhos sem autenticação.

Opções para aplicações hospedadas e fornecedores de login de terceiros

Ao autenticar e autorizar um aplicativo hospedado Blazor WebAssembly com um provedor de terceiros, há várias opções disponíveis para autenticar o usuário. Qual deles você escolhe depende do seu cenário.

Para mais informações, consulte A persistência de declarações e tokens adicionais dos provedores externos no ASP.NET Core.

Autenticar usuários para chamar apenas APIs de terceiros protegidas

Autentique o usuário com um fluxo OAuth do lado do cliente em relação ao provedor de API de terceiros:

builder.services.AddOidcAuthentication(options => { ... });

Neste cenário:

  • O servidor que hospeda o aplicativo não desempenha um papel.
  • As APIs no servidor não podem ser protegidas.
  • O aplicativo só pode chamar APIs de terceiros protegidas.

Autenticar utilizadores com um fornecedor de terceiros e chamar APIs protegidas no servidor host e no terceiro

Configure Identity com um provedor de login de terceiros. Obtenha os tokens necessários para acesso à API de terceiros e armazene-os.

Quando um usuário faz login, Identity coleta tokens de acesso e atualização como parte do processo de autenticação. Nesse ponto, há algumas abordagens disponíveis para fazer chamadas de API para APIs de terceiros.

Usar um token de acesso ao servidor para recuperar o token de acesso de terceiros

Use o token de acesso gerado no servidor para recuperar o token de acesso de terceiros a partir de um endpoint da API do servidor. A partir daí, use o token de acesso de terceiros para chamar recursos de API de terceiros diretamente do Identity cliente.

Não recomendamos esta abordagem. Essa abordagem requer tratar o token de acesso de terceiros como se fosse gerado para um cliente público. Em termos OAuth, o aplicativo público não tem um segredo de cliente porque não pode ser confiável para armazenar segredos com segurança, e o token de acesso é produzido para um cliente confidencial. Um cliente confidencial é um cliente que tem um segredo de cliente e se presume ser capaz de armazenar segredos com segurança.

  • O token de acesso de terceiros pode receber escopos adicionais para executar operações confidenciais com base no fato de que o terceiro emitiu o token para um cliente mais confiável.
  • Da mesma forma, os tokens de atualização não devem ser emitidos para um cliente que não é confiável, pois isso dá ao cliente acesso ilimitado, a menos que outras restrições sejam colocadas em prática.

Fazer chamadas de API do cliente para a API do servidor para chamar APIs de terceiros

Faça uma chamada de API do cliente para a API do servidor. No servidor, recupere o token de acesso para o recurso de API de terceiros e emita qualquer chamada necessária.

Recomendamos esta abordagem. Embora essa abordagem exija um salto de rede extra pelo servidor para chamar uma API de terceiros, ela resulta em uma experiência mais segura:

  • O servidor pode armazenar tokens de atualização e garantir que o aplicativo não perca o acesso a recursos de terceiros.
  • O aplicativo não pode vazar tokens de acesso do servidor que possam conter permissões mais confidenciais.

Utilizar interfaces OpenID Connect (OIDC) v2.0

A biblioteca de autenticação e Blazor os modelos de projeto usam endpoints OpenID Connect (OIDC) v1.0. Para usar um ponto de extremidade v2.0, configure a opção JWT Bearer JwtBearerOptions.Authority. No exemplo a seguir, ME-ID é configurado para v2.0 ao anexar um segmento v2.0 à propriedade Authority:

using Microsoft.AspNetCore.Authentication.JwtBearer;

...

builder.Services.Configure<JwtBearerOptions>(
    JwtBearerDefaults.AuthenticationScheme, 
    options =>
    {
        options.Authority += "/v2.0";
    });

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

{
  "Local": {
    "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 em JwtBearerOptions ou no ficheiro de configurações da aplicação (appsettings.json) com a chave Authority.

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 .

Configurar e usar gRPC em componentes

Para configurar uma Blazor WebAssembly aplicação para usar o framework ASP.NET Core gRPC:

Note

A pré-renderização é ativada por padrão em Blazor Web Apps, portanto, deve-se considerar que a renderização do componente acontece primeiro no servidor e depois no cliente. Qualquer estado pré-renderizado deve fluir para o cliente para que possa ser reutilizado. Para obter mais informações, consulte Persistência de estado pré-renderizado do núcleo ASP.NETBlazor.

Note

A pré-renderização é habilitada por padrão em aplicativos hospedados Blazor WebAssembly , portanto, você deve levar em conta a renderização do componente primeiro do servidor e, em seguida, do cliente. Qualquer estado pré-renderizado deve fluir para o cliente para que possa ser reutilizado. Para obter mais informações, consulte Integrar componentes do ASP.NET Core Razor com MVC ou Razor Pages em Blazor WebAssembly soluções hospedadas.

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;

...

builder.Services.AddScoped(sp =>
{
    var baseAddressMessageHandler = 
        sp.GetRequiredService<BaseAddressAuthorizationMessageHandler>();
    baseAddressMessageHandler.InnerHandler = new HttpClientHandler();
    var grpcWebHandler = 
        new GrpcWebHandler(GrpcWebMode.GrpcWeb, baseAddressMessageHandler);
    var channel = GrpcChannel.ForAddress(builder.HostEnvironment.BaseAddress, 
        new GrpcChannelOptions { HttpHandler = grpcWebHandler });

    return new Greeter.GreeterClient(channel);
});

Um componente no aplicativo cliente pode fazer chamadas gRPC usando o cliente gRPC (Grpc.razor):

@page "/grpc"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inject Greeter.GreeterClient GreeterClient

<h1>Invoke gRPC service</h1>

<p>
    <input @bind="name" placeholder="Type your name" />
    <button @onclick="GetGreeting" class="btn btn-primary">Call gRPC service</button>
</p>

Server response: <strong>@serverResponse</strong>

@code {
    private string name = "Bert";
    private string? serverResponse;

    private async Task GetGreeting()
    {
        try
        {
            var request = new HelloRequest { Name = name };
            var reply = await GreeterClient.SayHelloAsync(request);
            serverResponse = reply.Message;
        }
        catch (Grpc.Core.RpcException ex)
            when (ex.Status.DebugException is 
                AccessTokenNotAvailableException tokenEx)
        {
            tokenEx.Redirect();
        }
    }
}

Para usar a Status.DebugException propriedade, use a versão Grpc.Net.Client 2.30.0 ou posterior.

Para obter mais informações, consulte gRPC-Web em aplicações gRPC em ASP.NET Core.

Substituir a AuthenticationService implementação

As subsecções seguintes explicam como substituir:

  • Qualquer uma implementação JavaScript AuthenticationService.
  • A Biblioteca de Autenticação da Microsoft para JavaScript (MSAL.js).

Substitua qualquer implementação JavaScript AuthenticationService

Crie uma biblioteca JavaScript para lidar com seus detalhes de autenticação personalizados.

Warning

A orientação nesta seção é um detalhe de implementação do padrão RemoteAuthenticationService<TRemoteAuthenticationState,TAccount,TProviderOptions>. O código TypeScript nesta seção se aplica especificamente ao ASP.NET Core no .NET 7 e está sujeito a alterações sem aviso prévio em versões futuras do ASP.NET Core.

// .NET makes calls to an AuthenticationService object in the Window.
declare global {
  interface Window { AuthenticationService: AuthenticationService }
}

export interface AuthenticationService {
  // Init is called to initialize the AuthenticationService.
  public static init(settings: UserManagerSettings & AuthorizeServiceSettings, logger: any) : Promise<void>;

  // Gets the currently authenticated user.
  public static getUser() : Promise<{[key: string] : string }>;

  // Tries to get an access token silently.
  public static getAccessToken(options: AccessTokenRequestOptions) : Promise<AccessTokenResult>;

  // Tries to sign in the user or get an access token interactively.
  public static signIn(context: AuthenticationContext) : Promise<AuthenticationResult>;

  // Handles the sign-in process when a redirect is used.
  public static async completeSignIn(url: string) : Promise<AuthenticationResult>;

  // Signs the user out.
  public static signOut(context: AuthenticationContext) : Promise<AuthenticationResult>;

  // Handles the signout callback when a redirect is used.
  public static async completeSignOut(url: string) : Promise<AuthenticationResult>;
}

// The rest of these interfaces match their C# definitions.

export interface AccessTokenRequestOptions {
  scopes: string[];
  returnUrl: string;
}

export interface AccessTokenResult {
  status: AccessTokenResultStatus;
  token?: AccessToken;
}

export interface AccessToken {
  value: string;
  expires: Date;
  grantedScopes: string[];
}

export enum AccessTokenResultStatus {
  Success = 'Success',
  RequiresRedirect = 'RequiresRedirect'
}

export enum AuthenticationResultStatus {
  Redirect = 'Redirect',
  Success = 'Success',
  Failure = 'Failure',
  OperationCompleted = 'OperationCompleted'
};

export interface AuthenticationResult {
  status: AuthenticationResultStatus;
  state?: unknown;
  message?: string;
}

export interface AuthenticationContext {
  state?: unknown;
  interactiveRequest: InteractiveAuthenticationRequest;
}

export interface InteractiveAuthenticationRequest {
  scopes?: string[];
  additionalRequestParameters?: { [key: string]: any };
};

Você pode importar a biblioteca removendo a tag original <script> e adicionando uma <script> tag que carrega a biblioteca personalizada. O exemplo a seguir demonstra a substituição da tag padrão <script> por uma que carrega uma biblioteca nomeada CustomAuthenticationService.js da wwwroot/js pasta.

Dentro de wwwroot/index.html antes do script (Blazor) dentro da tag de fechamento _framework/blazor.webassembly.js</body>

- <script src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js"></script>
+ <script src="js/CustomAuthenticationService.js"></script>

Para obter mais informações, consulte AuthenticationService.ts no dotnet/aspnetcore repositório GitHub.

Note

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Alternar entre ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Substitua a Biblioteca de Autenticação da Microsoft para JavaScript (MSAL.js)

Se um aplicativo exigir uma versão personalizada da Biblioteca de Autenticação da Microsoft para JavaScript (MSAL.js), execute as seguintes etapas:

  1. Confirme se o sistema tem o SDK .NET do desenvolvedor mais recente ou obtenha e instale o SDK do desenvolvedor mais recente do .NET SDK: Installers and Binaries. A configuração de feeds NuGet internos não é necessária para este cenário.
  2. Configure o dotnet/aspnetcore repositório GitHub para desenvolvimento seguindo a documentação em Build ASP.NET Core from Source. Crie um fork e clone ou descarregue um ficheiro ZIP do dotnet/aspnetcore repositório GitHub.
  3. Abra o src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json arquivo e defina a versão desejada do @azure/msal-browser. Para obter uma lista de versões lançadas, visite o site do@azure/msal-browser npm e selecione a guia Versões.
  4. Construa o projeto Authentication.Msal na pasta src/Components/WebAssembly/Authentication.Msal/src com o comando yarn build num shell de comando.
  5. Se a aplicação usar recursos compactados (Brotli/Gzip), compacte o Interop/dist/Release/AuthenticationService.js arquivo.
  6. Copie o ficheiro AuthenticationService.js e as versões compactadas (.br/.gz) do ficheiro, se produzidas, da pasta Interop/dist/Release para a pasta publish/wwwroot/_content/Microsoft.Authentication.WebAssembly.Msal do aplicativo nos ativos publicados do aplicativo.

Passar opções de provedor personalizado

Defina uma classe para passar os dados para a biblioteca JavaScript subjacente.

Important

A estrutura da classe deve corresponder ao que a biblioteca espera quando o JSON é serializado com System.Text.Json.

O exemplo a seguir demonstra uma ProviderOptions classe com JsonPropertyName atributos que correspondem às expectativas de uma biblioteca de provedor personalizado hipotética:

public class ProviderOptions
{
    public string? Authority { get; set; }
    public string? MetadataUrl { get; set; }
    
    [JsonPropertyName("client_id")]
    public string? ClientId { get; set; }
    
    public IList<string> DefaultScopes { get; set; } = [ "openid", "profile" ];
        
    [JsonPropertyName("redirect_uri")]
    public string? RedirectUri { get; set; }
    
    [JsonPropertyName("post_logout_redirect_uri")]
    public string? PostLogoutRedirectUri { get; set; }
    
    [JsonPropertyName("response_type")]
    public string? ResponseType { get; set; }
    
    [JsonPropertyName("response_mode")]
    public string? ResponseMode { get; set; }
}

Registre as opções do provedor dentro do sistema DI e configure os valores apropriados:

builder.Services.AddRemoteAuthentication<RemoteAuthenticationState, RemoteUserAccount,
    ProviderOptions>(options => {
        options.ProviderOptions.Authority = "...";
        options.ProviderOptions.MetadataUrl = "...";
        options.ProviderOptions.ClientId = "...";
        options.ProviderOptions.DefaultScopes = [ "openid", "profile", "myApi" ];
        options.ProviderOptions.RedirectUri = "https://localhost:5001/authentication/login-callback";
        options.ProviderOptions.PostLogoutRedirectUri = "https://localhost:5001/authentication/logout-callback";
        options.ProviderOptions.ResponseType = "...";
        options.ProviderOptions.ResponseMode = "...";
    });

O exemplo anterior define URIs de redirecionamento com cadeias de texto regulares. Estão disponíveis as seguintes alternativas:

Recursos adicionais