Compartilhar via


Proteger um ASP.NET Core Blazor Web App com OIDC (OpenID Connect)

Note

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

Este artigo descreve como proteger um Blazor Web App com OpenID Connect (OIDC) usando um aplicativo de exemplo no dotnet/blazor-samples repositório GitHub (.NET 8 ou posterior) (como baixar).

Para o Microsoft Entra ID ou Azure AD B2C, você pode usar AddMicrosoftIdentityWebApp da Microsoft Identity Web (Microsoft.Identity.Web pacote NuGet, documentação da API), que adiciona os manipuladores de OIDC e Cookie autenticação com os padrões apropriados. O aplicativo de exemplo e as diretrizes neste artigo não usam a Microsoft Identity Web. As diretrizes demonstram como configurar o manipulador OIDC manualmente para qualquer provedor OIDC. Para obter mais informações sobre como implementar a Microsoft Identity Web, consulte Proteja um ASP.NET Core Blazor Web App com Microsoft Entra ID.

Esta versão do artigo aborda a implementação do OIDC sem adotar o padrão Backend para Frontend (BFF) junto com um aplicativo que adota a renderização interativa automática global (projetos de servidor e .Client). O padrão BFF é útil para fazer solicitações autenticadas a serviços externos. Altere o seletor de versão do artigo para o padrão BFF se a especificação do aplicativo solicitar a adoção do padrão BFF.

A seguinte especificação é adotada:

  • O Blazor Web App usa o modo de renderização automática com interatividade global.
  • Os serviços personalizados de provedor de estado de autenticação são usados pelo servidor e pelos aplicativos cliente para capturar o estado de autenticação do usuário e transferi-lo entre o servidor e o cliente.
  • Este aplicativo é um ponto de partida para qualquer fluxo de autenticação OIDC. OIDC é configurado manualmente no aplicativo e não depende do Microsoft Entra ID ou dos pacotes Microsoft Identity Web, nem o aplicativo de exemplo requer hospedagem no Microsoft Azure. No entanto, o aplicativo de exemplo pode ser usado com o Entra, Microsoft Identity Web e hospedado no Azure.
  • Atualização automática de tokens de modo não interativo.
  • Um projeto de API Web separado demonstra uma chamada de API Web segura para dados meteorológicos.

Para obter uma experiência alternativa usando a Biblioteca de Autenticação da Microsoft para .NET, Microsoft Identity Web e Microsoft Entra ID, consulte Secure an ASP.NET Core Blazor Web App with Microsoft Entra ID.

Solução de exemplo

O aplicativo de exemplo consiste nos seguintes projetos:

  • BlazorWebAppOidc: projeto do Blazor Web App lado do servidor, que contém um exemplo de endpoint de Minimal API para dados meteorológicos.
  • BlazorWebAppOidc.Client: projeto do cliente do Blazor Web App.
  • MinimalApiJwt: API Web de back-end com um endpoint Minimal API para dados meteorológicos.

Acesse o exemplo por meio da pasta de versão mais recente no Blazor repositório de exemplos com o link a seguir. O exemplo está na BlazorWebAppOidc pasta para .NET 8 ou posterior.

Inicie a solução a partir do Aspire/Aspire.AppHost projeto.

Exibir ou baixar o código de exemplo (como baixar)

Recursos de solução de exemplo:

  • Atualização automática de token não interativo com a ajuda de um atualizador cookie personalizado (CookieOidcRefresher.cs).

  • Os dados meteorológicos são tratados por um endpoint de API mínima (/weather-forecast) no arquivo Program(Program.cs) do projeto MinimalApiJwt. O endpoint requer autorização ao chamar RequireAuthorization. Para todos os controladores que você adicionar ao projeto, adicione o [Authorize] atributo ao controlador ou à ação. Para obter mais informações sobre como exigir autorização em todo o aplicativo por meio de uma política de autorização e optar por não exigir autorização em um subconjunto de pontos de extremidade públicos, consulte as diretrizes das Páginas do OIDCRazor.

  • O aplicativo chama com segurança uma API Web para dados meteorológicos:

    • Ao renderizar o componente Weather no servidor, o componente usa o servidor ServerWeatherForecaster para obter dados do clima da API Web no projeto MinimalApiJwt usando um DelegatingHandler (TokenHandler) que anexa o token de acesso à solicitação HttpContext.
    • Quando o componente é renderizado no cliente, o componente usa a implementação do serviço ClientWeatherForecaster, que utiliza um HttpClient preconfigurado (no arquivo do projeto cliente Program) para fazer a chamada à API web do projeto do ServerWeatherForecaster servidor.
  • A classe PersistingAuthenticationStateProvider (PersistingAuthenticationStateProvider.cs) é um AuthenticationStateProvider do lado do servidor que usa PersistentComponentState para transmitir o estado de autenticação para o cliente, que é então corrigido para o tempo de vida do aplicativo WebAssembly.

Para obter mais informações sobre chamadas de API (Web) usando abstrações de serviço em Blazor Web Apps, consulte Chamar uma API Web de um aplicativo ASP.NET CoreBlazor.

Terminologia e diretrizes do provedor OIDC

Embora você não seja obrigado a adotar o Microsoft Entra (ME-ID) como o provedor OIDC para usar o aplicativo de exemplo e as diretrizes neste artigo, este artigo descreve as configurações para ME-ID usando nomes encontrados na documentação da Microsoft e nos portais do Azure/Entra. As configurações de OIDC têm nomenclatura semelhante entre provedores OIDC. Ao usar um provedor OIDC de terceiros, use a documentação do provedor em conjunto com as diretrizes neste artigo para registros de aplicativo e API Web.

Registros de aplicativo do Microsoft Entra ID

É recomendável usar registros separados para aplicativos e APIs Web, mesmo quando os aplicativos e AS APIs Web estão na mesma solução. As diretrizes a seguir são para o BlazorWebAppOidc aplicativo e MinimalApiJwt a API Web da solução de exemplo, mas as mesmas diretrizes geralmente se aplicam a quaisquer registros baseados em Entra para aplicativos e APIs Web.

Para obter diretrizes de registro de aplicativo e API Web, consulte Registrar um aplicativo na ID do Microsoft Entra.

Registre a API Web (MinimalApiJwt) primeiro para que você possa conceder acesso à API Web ao registrar o aplicativo. As IDs do locatário e do cliente da API Web são usadas para configurar a API Web em seu arquivo Program. Depois de registrar a API Web, exponha a API Web em registros de aplicativo>Expor uma API com um nome de escopo de Weather.Get. Registre o URI da ID do Aplicativo para uso na configuração do aplicativo.

Em seguida, registre o aplicativo (BlazorWebAppOidc/BlazorWebApOidc.Client) com uma configuração de plataforma Web e um URI de Redirecionamento de https://localhost/signin-oidc (uma porta não é necessária). A ID do inquilino do aplicativo e a ID do cliente, juntamente com o endereço base da API Web, o URI da ID do App e o nome do escopo de clima, são usados para configurar o aplicativo em seu arquivo Program. Conceda permissões da API para acessar a API web em Registros de Aplicativo>Permissões da API. Se a especificação de segurança do aplicativo o chamar, você poderá conceder consentimento do administrador para que a organização acesse a API Web. Usuários e grupos autorizados são atribuídos ao registro do aplicativo em Registros de aplicativos>Aplicativos empresariais.

Na configuração de registro de aplicativo de concessão implícita e fluxos híbridos do Portal Entra ou do Azure, não selecione nenhuma das caixas de seleção para o ponto de extremidade de autorização retornar tokens de acesso ou tokens de identificação. O manipulador do OpenID Connect solicita automaticamente os tokens apropriados usando o código retornado do endpoint de autorização.

Crie um segredo do cliente no registro do aplicativo no portal do Entra ou do Azure (Gerenciar>Certificados &segredos>Novo segredo do cliente). Mantenha o segredo do cliente Valor para usar na próxima seção.

Diretrizes adicionais de configuração do Entra para configurações específicas são fornecidas posteriormente neste artigo.

Estabelecer o segredo do cliente

Esta seção se aplica apenas ao Blazor Web App projeto de servidor (BlazorWebAppOidc).

Warning

Não armazene segredos do aplicativo, cadeias de conexão, credenciais, senhas, PINs (números de identificação pessoal), código C#/.NET privado ou chaves/tokens privados no código do lado do cliente, que é sempre inseguro. Em ambientes de teste/preparo e produção, o código do lado do Blazor servidor e as APIs Web devem usar fluxos de autenticação seguros que evitam a manutenção de credenciais no código do projeto ou nos arquivos de configuração. Fora dos testes de desenvolvimento local, recomendamos evitar o uso de variáveis de ambiente para armazenar dados confidenciais, pois as variáveis de ambiente não são a abordagem mais segura. Para testes de desenvolvimento local, a ferramenta Gerenciador de Segredos é recomendada para proteger dados confidenciais. Para obter mais informações, consulte Manter com segurança dados confidenciais e credenciais.

Para testes de desenvolvimento local, use a ferramenta Gerenciador de Segredos para armazenar o Blazor segredo do cliente do projeto do servidor na chave Authentication:Schemes:MicrosoftOidc:ClientSecret de configuração.

O Blazor projeto do servidor não foi inicializado para a ferramenta do Gerenciador de Segredos. Use um shell de comando, como o shell de comando do PowerShell do Desenvolvedor no Visual Studio, para executar o comando a seguir. Antes de executar o comando, altere o diretório com o cd comando para o diretório do projeto do servidor. O comando estabelece um identificador de segredos do usuário (<UserSecretsId> no arquivo de projeto do aplicativo do servidor):

dotnet user-secrets init

Execute o comando a seguir para definir o segredo do cliente. O marcador {SECRET} é o segredo do cliente obtido do registro do aplicativo:

dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}"

Se estiver usando o Visual Studio, você poderá confirmar se o segredo está definido clicando com o botão direito do mouse no projeto do servidor no Gerenciador de Soluções e selecionando Gerenciar Segredos do Usuário.

MinimalApiJwt projeto

O projeto MinimalApiJwt é uma API Web de back-end para vários projetos de front-end. O projeto configura um endpoint Minimal API para dados meteorológicos.

O arquivo MinimalApiJwt.http pode ser usado para testar a solicitação de dados meteorológicos. Observe que o projeto MinimalApiJwt deve estar em execução para testar o ponto de extremidade e o ponto de extremidade está codificado no arquivo. Para obter mais informações, consulte Usar arquivos .http no Visual Studio 2022.

O projeto inclui pacotes e configuração para produzir documentos OpenAPI.

O projeto inclui pacotes e configuração para produzir documentos OpenAPI e a interface do usuário do Swagger no ambiente de desenvolvimento. Para obter mais informações, consulte Usar os documentos OpenAPI gerados.

O projeto cria um endpoint Minimal API para dados meteorológicos.

app.MapGet("/weather-forecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
}).RequireAuthorization();

Configure o projeto na JwtBearerOptions da chamada AddJwtBearer no arquivo Program do projeto.

Configura Authority a autoridade para fazer chamadas OIDC. É recomendável usar um registro de aplicativo separado para o MinimalApiJwt projeto. A autoridade corresponde ao issurer (iss) do JWT retornado pelo provedor de identidade.

jwtOptions.Authority = "{AUTHORITY}";

O formato da Autoridade depende do tipo de locatário em uso. Os exemplos a seguir para o Microsoft Entra ID usam uma ID do Locatário de aaaabbbb-0000-cccc-1111-dddd2222eeee.

ME-ID exemplo da Autoridade do Inquilino:

jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee";

Exemplo da Autoridade de Locatário do AAD B2C:

jwtOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0";

Audience configura o público-alvo de qualquer token OIDC recebido.

jwtOptions.Audience = "{APP ID URI}";

Note

Ao usar a ID do Microsoft Entra, corresponda apenas o valor ao caminho do URI da ID do Aplicativo configurado ao adicionar o Weather.Get escopo em Expor uma API no portal do Entra ou do Azure. Não inclua o nome do escopo, "Weather.Get", no valor.

O formato do Público-Alvo depende do tipo de locatário em uso. Os exemplos a seguir de ID do Microsoft Entra usam uma ID de locatário de contoso e uma ID de cliente de 11112222-bbbb-3333-cccc-4444dddd5555.

ME-ID exemplo de URI de ID do aplicativo do locatário:

jwtOptions.Audience = "api://11112222-bbbb-3333-cccc-4444dddd5555";

Exemplo de URI da ID do aplicativo de locatário B2C do AAD:

jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555";

Blazor Web App projeto de servidor (BlazorWebAppOidc)

O projeto BlazorWebAppOidc é o projeto do lado do servidor do Blazor Web App.

Um DelegatingHandler (TokenHandler) gerencia a anexação do token de acesso de um usuário a solicitações de saída. O manipulador de token só é executado durante a renderização estática do lado do servidor (SSR estático), portanto, o uso HttpContext é seguro nesse cenário. Para obter mais informações, consulte IHttpContextAccessor/HttpContext em aplicativos do ASP.NET Core Blazor e cenários de segurança do lado do servidor do ASP.NET Core e Blazor Web App adicionais.

TokenHandler.cs:

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

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

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

No arquivo do projeto Program, o manipulador de tokens (TokenHandler) é registrado como um serviço e especificado como o manipulador de mensagens com AddHttpMessageHandler para realizar solicitações seguras à API web de back-end MinimalApiJwt usando um cliente HTTP nomeado ("ExternalApi").

builder.Services.AddScoped<TokenHandler>();

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

No arquivo do projeto appsettings.json, configure o URI da API externa.

"ExternalApiUri": "{BASE ADDRESS}"

Example:

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

A configuração OpenIdConnectOptions a seguir é encontrada no arquivo Program do projeto na chamada para AddOpenIdConnect:

PushedAuthorizationBehavior: controla o suporte a PAR (Solicitações de Autorização Enviadas por Push). Por padrão, a configuração é usar PAR se o documento de descoberta do provedor de identidade (geralmente encontrado em .well-known/openid-configuration) indicar suporte para PAR. Se você quiser exigir suporte par para o aplicativo, poderá atribuir um valor de PushedAuthorizationBehavior.Require. O PAR não é compatível com o Microsoft Entra e não há planos para o Entra dar suporte a ele no futuro.

oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.UseIfAvailable;

SignInScheme: define o esquema de autenticação correspondente ao middleware responsável por persistir a identidade do usuário após uma autenticação bem-sucedida. O manipulador do OIDC precisa usar um esquema de entrada capaz de persistir as credenciais do usuário entre solicitações. A linha a seguir está presente apenas para fins de demonstração. Se omitido, DefaultSignInScheme será usado como um valor de fallback.

oidcOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;

Escopos para openid e profile (Scope) (Opcional): os escopos openid e profile também são configurados por padrão porque são necessários para que o manipulador do OIDC funcione, mas eles podem precisar ser adicionados novamente se os escopos forem incluídos na configuração Authentication:Schemes:MicrosoftOidc:Scope. Para obter diretrizes gerais de configuração, consulte Configuração no ASP.NET Core e Configuração do ASP.NET CoreBlazor.

oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);

SaveTokens: define se os tokens de acesso e atualização devem ser armazenados no AuthenticationProperties após uma autorização bem-sucedida. Essa propriedade é definida como true para que o token de atualização seja armazenado para atualização de token não interativa.

oidcOptions.SaveTokens = true;

Escopo para acesso offline (Scope): o escopo offline_access é necessário para o token de atualização.

oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);

Authority e ClientId: define a autoridade e a ID do cliente para chamadas ao OIDC.

oidcOptions.Authority = "{AUTHORITY}";
oidcOptions.ClientId = "{CLIENT ID}";

O exemplo a seguir usa um ID de locatário aaaabbbb-0000-cccc-1111-dddd2222eeee e um ID de cliente 00001111-aaaa-2222-bbbb-3333cccc4444:

oidcOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0";
oidcOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";

Para aplicativos multilocatários, a autoridade "comum" deve ser usada. Você também pode usar a autoridade "comum" para aplicativos de locatário único, mas um IssuerValidator personalizado é necessário, conforme será mostrado posteriormente nesta seção.

oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0";

ResponseType: configura o manipulador do OIDC para executar apenas o fluxo do código de autorização. Concessões implícitas e fluxos híbridos são desnecessários nesse modo. O manipulador do OIDC solicita automaticamente os tokens apropriados usando o código retornado do ponto de extremidade da autorização.

oidcOptions.ResponseType = OpenIdConnectResponseType.Code;

MapInboundClaims e a configuração de NameClaimType e RoleClaimType: muitos servidores OIDC usam "name" e "role" em vez dos padrões SOAP/WS-Fed em ClaimTypes. Quando MapInboundClaims está definido como false, o manipulador não executa mapeamentos de declarações e os nomes das declarações do JWT são usados diretamente pelo aplicativo. O exemplo a seguir define o tipo de declaração de função como "roles", que é apropriado para a ID do Microsoft Entra (ME-ID). Consulte a documentação do seu provedor de identidade para obter mais informações.

Note

MapInboundClaims deve ser definido como false para a maioria dos provedores OIDC, o que impede a renomeação de declarações.

oidcOptions.MapInboundClaims = false;
oidcOptions.TokenValidationParameters.NameClaimType = "name";
oidcOptions.TokenValidationParameters.RoleClaimType = "roles";

Configuração do caminho: os caminhos devem corresponder ao URI de redirecionamento (caminho de retorno de chamada do logon) e aos caminhos de redirecionamento de logoff (caminho de retorno de chamada de saída) configurados ao registrar o aplicativo com o provedor OIDC. No portal do Azure, os caminhos são configurados na folha Autenticação do registro do aplicativo. Os caminhos de entrada e saída devem ser registrados como URIs de redirecionamento. Os valores padrão são /signin-oidc e /signout-callback-oidc.

CallbackPath: o caminho da solicitação dentro do caminho base do aplicativo no qual o agente do usuário é retornado.

Configure o caminho de retorno de logout no registro do provedor OIDC do aplicativo. No exemplo a seguir, o placeholder {PORT} é a porta do aplicativo:

https://localhost:{PORT}/signin-oidc

Note

Uma porta não é necessária para endereços localhost ao usar o Microsoft Entra ID. A maioria dos outros provedores OIDC exige a porta correta.

SignedOutCallbackPath (chave de configuração: "SignedOutCallbackPath"): o caminho da solicitação dentro do caminho base do aplicativo, interceptado pelo manipulador OIDC, onde o agente do usuário é inicialmente redirecionado após sair do provedor de identidade. O aplicativo de exemplo não define um valor para o caminho porque o valor padrão "/signout-callback-oidc" é usado. Depois de interceptar a solicitação, o manipulador OIDC redireciona para o SignedOutRedirectUri ou RedirectUri, se especificado.

Configure o caminho de retorno de logout no registro do provedor OIDC do aplicativo. No exemplo a seguir, o placeholder {PORT} é a porta do aplicativo:

https://localhost:{PORT}/signout-callback-oidc

Note

Ao usar a ID do Microsoft Entra, defina o caminho nas entradas de URI de Redirecionamento da configuração da plataforma Web no portal do Entra ou do Azure. Uma porta não é necessária para endereços localhost ao usar o Entra. A maioria dos outros provedores OIDC exige a porta correta. Se você não adicionar o URI de caminho de retorno de chamada após desconexão ao registro do aplicativo no Entra, o Entra se recusará a redirecionar o usuário de volta para o aplicativo e apenas solicitará que ele feche a janela do navegador.

RemoteSignOutPath: as solicitações recebidas nesse caminho fazem com que o manipulador invoque a saída usando o esquema de saída.

No exemplo a seguir, o placeholder {PORT} é a porta do aplicativo:

https://localhost/signout-oidc

Note

Ao usar a ID do Microsoft Entra, defina a URL de logoff do front-channel no portal do Entra ou do Azure. Uma porta não é necessária para endereços localhost ao usar o Entra. A maioria dos outros provedores OIDC exige a porta correta.

oidcOptions.CallbackPath = new PathString("{PATH}");
oidcOptions.SignedOutCallbackPath = new PathString("{PATH}");
oidcOptions.RemoteSignOutPath = new PathString("{PATH}");

Exemplos (valores padrão):

oidcOptions.CallbackPath = new PathString("/signin-oidc");
oidcOptions.SignedOutCallbackPath = new PathString("/signout-callback-oidc");
oidcOptions.RemoteSignOutPath = new PathString("/signout-oidc");

(Microsoft Azure somente com o ponto de extremidade "comum") TokenValidationParameters.IssuerValidator: muitos provedores OIDC trabalham com o validador do emissor padrão, mas precisamos considerar o emissor parametrizado com a ID do Locatário ({TENANT ID}) retornada por https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration. Para obter mais informações, consulte SecurityTokenInvalidIssuerException com OpenID Connect e o endpoint "common" do Azure AD (AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet nº 1731).

Somente para aplicativos que usam o Microsoft Entra ID ou o Azure AD B2C com o ponto de extremidade "comum":

var microsoftIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(oidcOptions.Authority);
oidcOptions.TokenValidationParameters.IssuerValidator = microsoftIssuerValidator.Validate;

Blazor Web App projeto cliente (BlazorWebAppOidc.Client)

O projeto BlazorWebAppOidc.Client é o projeto cliente do Blazor Web App.

O cliente chama AddAuthenticationStateDeserialization para desserializar e usar o estado de autenticação passado pelo servidor. O estado de autenticação é fixado para a duração do aplicativo WebAssembly.

A classe PersistentAuthenticationStateProvider (PersistentAuthenticationStateProvider.cs) é uma AuthenticationStateProvider do lado do cliente que determina o estado de autenticação do usuário procurando dados persistidos na página quando ele foi renderizado no servidor. O estado de autenticação é fixado para a duração do aplicativo WebAssembly.

Se o usuário precisar fazer logon ou logoff, será necessário recarregar a página inteira.

O aplicativo de exemplo fornece apenas um nome de usuário e email para fins de exibição.

Para o Microsoft Entra ID ou Azure AD B2C, você pode usar AddMicrosoftIdentityWebApp da Microsoft Identity Web (Microsoft.Identity.Web pacote NuGet, documentação da API), que adiciona os manipuladores de OIDC e Cookie autenticação com os padrões apropriados. O aplicativo de exemplo e as diretrizes neste artigo não usam a Microsoft Identity Web. As diretrizes demonstram como configurar o manipulador OIDC manualmente para qualquer provedor OIDC. Para obter mais informações sobre como implementar a Microsoft Identity Web, consulte Proteja um ASP.NET Core Blazor Web App com Microsoft Entra ID.

Esta versão do artigo aborda a implementação do OIDC sem a adoção do padrão Back-end para Front-end (BFF) com um aplicativo que adota a renderização global do Interactive Server (projeto único). O padrão BFF é útil para fazer solicitações autenticadas a serviços externos. Altere o seletor de versão do artigo para o padrão BFF se a especificação do aplicativo solicitar a adoção do padrão BFF com a renderização automática interativa global.

A seguinte especificação é adotada:

  • O Blazor Web App usa o modo de renderização do servidor com interatividade global.
  • Este aplicativo é um ponto de partida para qualquer fluxo de autenticação OIDC. OIDC é configurado manualmente no aplicativo e não depende do Microsoft Entra ID ou dos pacotes Microsoft Identity Web, nem o aplicativo de exemplo requer hospedagem no Microsoft Azure. No entanto, o aplicativo de exemplo pode ser usado com o Entra, Microsoft Identity Web e hospedado no Azure.
  • Atualização automática de tokens de modo não interativo.
  • Um projeto de API Web separado demonstra uma chamada de API Web segura para dados meteorológicos.

Para obter uma experiência alternativa usando a Biblioteca de Autenticação da Microsoft para .NET, Microsoft Identity Web e Microsoft Entra ID, consulte Secure an ASP.NET Core Blazor Web App with Microsoft Entra ID.

Solução de exemplo

O aplicativo de exemplo consiste nos seguintes projetos:

  • BlazorWebAppOidcServer: Blazor Web App projeto do lado do servidor (renderização global do Servidor Interativo).
  • MinimalApiJwt: API Web de back-end com um endpoint Minimal API para dados meteorológicos.

Acesse o exemplo por meio da pasta de versão mais recente no Blazor repositório de exemplos com o link a seguir. O exemplo está na BlazorWebAppOidcServer pasta para .NET 8 ou posterior.

Exibir ou baixar o código de exemplo (como baixar)

Registros de aplicativo do Microsoft Entra ID

É recomendável usar registros separados para aplicativos e APIs Web, mesmo quando os aplicativos e AS APIs Web estão na mesma solução. As diretrizes a seguir são para o BlazorWebAppOidcServer aplicativo e MinimalApiJwt a API Web da solução de exemplo, mas as mesmas diretrizes geralmente se aplicam a quaisquer registros baseados em Entra para aplicativos e APIs Web.

Registre a API Web (MinimalApiJwt) primeiro para que você possa conceder acesso à API Web ao registrar o aplicativo. As IDs do locatário e do cliente da API Web são usadas para configurar a API Web em seu arquivo Program. Depois de registrar a API Web, exponha a API Web em registros de aplicativo>Expor uma API com um nome de escopo de Weather.Get. Registre o URI da ID do Aplicativo para uso na configuração do aplicativo.

Em seguida, registre o aplicativo (BlazorWebAppOidcServer) com uma configuração de plataforma Web e um URI de Redirecionamento de https://localhost/signin-oidc, não é necessário especificar uma porta. A ID do inquilino do aplicativo e a ID do cliente, juntamente com o endereço base da API Web, o URI da ID do App e o nome do escopo de clima, são usados para configurar o aplicativo em seu arquivo Program. Conceda permissões da API para acessar a API web em Registros de Aplicativo>Permissões da API. Se a especificação de segurança do aplicativo o chamar, você poderá conceder consentimento do administrador para que a organização acesse a API Web. Usuários e grupos autorizados são atribuídos ao registro do aplicativo em Registros de aplicativos>Aplicativos empresariais.

Na configuração de registro de aplicativo de concessão implícita e fluxos híbridos do Portal Entra ou do Azure, não selecione nenhuma das caixas de seleção para o ponto de extremidade de autorização retornar tokens de acesso ou tokens de identificação. O manipulador do OpenID Connect solicita automaticamente os tokens apropriados usando o código retornado do endpoint de autorização.

Crie um segredo do cliente no registro do aplicativo no portal do Entra ou do Azure (Gerenciar>Certificados &segredos>Novo segredo do cliente). Mantenha o segredo do cliente Valor para usar na próxima seção.

Diretrizes adicionais de configuração do Entra para configurações específicas são fornecidas posteriormente neste artigo.

Estabelecer o segredo do cliente

Esta seção se aplica apenas ao Blazor Web App projeto de servidor (BlazorWebAppOidcServer).

Warning

Não armazene segredos do aplicativo, cadeias de conexão, credenciais, senhas, PINs (números de identificação pessoal), código C#/.NET privado ou chaves/tokens privados no código do lado do cliente, que é sempre inseguro. Em ambientes de teste/preparo e produção, o código do lado do Blazor servidor e as APIs Web devem usar fluxos de autenticação seguros que evitam a manutenção de credenciais no código do projeto ou nos arquivos de configuração. Fora dos testes de desenvolvimento local, recomendamos evitar o uso de variáveis de ambiente para armazenar dados confidenciais, pois as variáveis de ambiente não são a abordagem mais segura. Para testes de desenvolvimento local, a ferramenta Gerenciador de Segredos é recomendada para proteger dados confidenciais. Para obter mais informações, consulte Manter com segurança dados confidenciais e credenciais.

Para testes de desenvolvimento local, use a ferramenta Gerenciador de Segredos para armazenar o Blazor segredo do cliente do projeto do servidor na chave Authentication:Schemes:MicrosoftOidc:ClientSecret de configuração.

O Blazor projeto do servidor não foi inicializado para a ferramenta do Gerenciador de Segredos. Use um shell de comando, como o shell de comando do PowerShell do Desenvolvedor no Visual Studio, para executar o comando a seguir. Antes de executar o comando, altere o diretório com o cd comando para o diretório do projeto do servidor. O comando estabelece um identificador de segredos do usuário (<UserSecretsId> no arquivo de projeto do aplicativo):

dotnet user-secrets init

Execute o comando a seguir para definir o segredo do cliente. O marcador {SECRET} é o segredo do cliente obtido do registro do aplicativo:

dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}"

Se estiver usando o Visual Studio, você poderá confirmar se o segredo está definido clicando com o botão direito do mouse no projeto no Gerenciador de Soluções e selecionando Gerenciar Segredos do Usuário.

MinimalApiJwt projeto

O projeto MinimalApiJwt é uma API Web de back-end para vários projetos de front-end. O projeto configura um endpoint Minimal API para dados meteorológicos.

O arquivo MinimalApiJwt.http pode ser usado para testar a solicitação de dados meteorológicos. Observe que o projeto MinimalApiJwt deve estar em execução para testar o ponto de extremidade e o ponto de extremidade está codificado no arquivo. Para obter mais informações, consulte Usar arquivos .http no Visual Studio 2022.

O projeto inclui pacotes e configuração para produzir documentos OpenAPI.

O projeto inclui pacotes e configuração para produzir documentos OpenAPI e a interface do usuário do Swagger no ambiente de desenvolvimento. Para obter mais informações, consulte Usar os documentos OpenAPI gerados.

O projeto cria um endpoint Minimal API para dados meteorológicos.

app.MapGet("/weather-forecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
}).RequireAuthorization();

Configure o projeto na JwtBearerOptions da chamada AddJwtBearer no arquivo Program do projeto.

Configura Authority a autoridade para fazer chamadas OIDC. É recomendável usar um registro de aplicativo separado para o MinimalApiJwt projeto. A autoridade corresponde ao issurer (iss) do JWT retornado pelo provedor de identidade.

jwtOptions.Authority = "{AUTHORITY}";

O formato da Autoridade depende do tipo de locatário em uso. Os exemplos a seguir para o Microsoft Entra ID usam uma ID do Locatário de aaaabbbb-0000-cccc-1111-dddd2222eeee.

ME-ID exemplo da Autoridade do Inquilino:

jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee";

Exemplo da Autoridade de Locatário do AAD B2C:

jwtOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0";

Audience configura o público-alvo de qualquer token OIDC recebido.

jwtOptions.Audience = "{APP ID URI}";

Note

Ao usar a ID do Microsoft Entra, corresponda apenas o valor ao caminho do URI da ID do Aplicativo configurado ao adicionar o Weather.Get escopo em Expor uma API no portal do Entra ou do Azure. Não inclua o nome do escopo, "Weather.Get", no valor.

O formato do Público-Alvo depende do tipo de locatário em uso. Os exemplos a seguir de ID do Microsoft Entra usam uma ID de locatário de contoso e uma ID de cliente de 11112222-bbbb-3333-cccc-4444dddd5555.

ME-ID exemplo de URI de ID do aplicativo do locatário:

jwtOptions.Audience = "api://11112222-bbbb-3333-cccc-4444dddd5555";

Exemplo de URI da ID do aplicativo de locatário B2C do AAD:

jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555";

BlazorWebAppOidcServer projeto

A renovação automática de tokens não interativos é gerenciada por um atualizador personalizado cookie (CookieOidcRefresher.cs).

Um DelegatingHandler (TokenHandler) gerencia a anexação do token de acesso de um usuário a solicitações de saída. O manipulador de token só é executado durante a renderização estática do lado do servidor (SSR estático), portanto, o uso HttpContext é seguro nesse cenário. Para obter mais informações, consulte IHttpContextAccessor/HttpContext em aplicativos do ASP.NET Core Blazor e cenários de segurança do lado do servidor do ASP.NET Core e Blazor Web App adicionais.

TokenHandler.cs:

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

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

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

No arquivo do projeto Program, o manipulador de tokens (TokenHandler) é registrado como um serviço e especificado como o manipulador de mensagens com AddHttpMessageHandler para realizar solicitações seguras à API web de back-end MinimalApiJwt usando um cliente HTTP nomeado ("ExternalApi").

builder.Services.AddScoped<TokenHandler>();

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

O Weather componente usa o [Authorize] atributo para impedir o acesso não autorizado. Para obter mais informações sobre como exigir autorização em todo o aplicativo por meio de uma política de autorização e optar por não exigir autorização em um subconjunto de pontos de extremidade públicos, consulte as diretrizes das Páginas do OIDCRazor.

O ExternalApi cliente HTTP é usado para fazer uma solicitação de dados meteorológicos para a API Web segura. No OnInitializedAsync evento do ciclo de vida de Weather.razor:

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

forecasts = await response.Content.ReadFromJsonAsync<WeatherForecast[]>() ??
    throw new IOException("No weather forecast!");

No arquivo do projeto appsettings.json, configure o URI da API externa.

"ExternalApiUri": "{BASE ADDRESS}"

Example:

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

A configuração OpenIdConnectOptions a seguir é encontrada no arquivo Program do projeto na chamada para AddOpenIdConnect:

PushedAuthorizationBehavior: controla o suporte a PAR (Solicitações de Autorização Enviadas por Push). Por padrão, a configuração é usar PAR se o documento de descoberta do provedor de identidade (geralmente encontrado em .well-known/openid-configuration) indicar suporte para PAR. Se você quiser exigir suporte par para o aplicativo, poderá atribuir um valor de PushedAuthorizationBehavior.Require. O PAR não é compatível com o Microsoft Entra e não há planos para o Entra dar suporte a ele no futuro.

oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.UseIfAvailable;

SignInScheme: define o esquema de autenticação correspondente ao middleware responsável por persistir a identidade do usuário após uma autenticação bem-sucedida. O manipulador do OIDC precisa usar um esquema de entrada capaz de persistir as credenciais do usuário entre solicitações. A linha a seguir está presente apenas para fins de demonstração. Se omitido, DefaultSignInScheme será usado como um valor de fallback.

oidcOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;

Escopos para openid e profile (Scope) (Opcional): os escopos openid e profile também são configurados por padrão porque são necessários para que o manipulador do OIDC funcione, mas eles podem precisar ser adicionados novamente se os escopos forem incluídos na configuração Authentication:Schemes:MicrosoftOidc:Scope. Para obter diretrizes gerais de configuração, consulte Configuração no ASP.NET Core e Configuração do ASP.NET CoreBlazor.

oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);

Configure o Weather.Get escopo para acessar a API Web externa para dados meteorológicos. No domínio do locatário ME-ID, o exemplo a seguir baseia-se no uso do Entra ID. No exemplo a seguir, o marcador de posição {APP ID URI} é encontrado no portal do Entra ou do Azure onde a API Web é exposta. Para qualquer outro provedor de identidade, use o escopo apropriado.

oidcOptions.Scope.Add("{APP ID URI}/Weather.Get");

O formato do escopo depende do tipo de locatário em uso. Nos exemplos a seguir, o Domínio do Locatário é contoso.onmicrosoft.come a ID do cliente é 11112222-bbbb-3333-cccc-4444dddd5555.

ME-ID exemplo de URI de ID do aplicativo do locatário:

oidcOptions.Scope.Add("api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get");

Exemplo de URI da ID do aplicativo de locatário B2C do AAD:

oidcOptions.Scope.Add("https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get");

SaveTokens: define se os tokens de acesso e atualização devem ser armazenados no AuthenticationProperties após uma autorização bem-sucedida. Essa propriedade é definida como true para que o token de atualização seja armazenado para atualização de token não interativa.

oidcOptions.SaveTokens = true;

Escopo para acesso offline (Scope): o escopo offline_access é necessário para o token de atualização.

oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);

Authority e ClientId: define a autoridade e a ID do cliente para chamadas ao OIDC.

oidcOptions.Authority = "{AUTHORITY}";
oidcOptions.ClientId = "{CLIENT ID}";

O exemplo a seguir usa um ID de locatário aaaabbbb-0000-cccc-1111-dddd2222eeee e um ID de cliente 00001111-aaaa-2222-bbbb-3333cccc4444:

oidcOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0";
oidcOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";

Para aplicativos multilocatários, a autoridade "comum" deve ser usada. Você também pode usar a autoridade "comum" para aplicativos de locatário único, mas um IssuerValidator personalizado é necessário, conforme será mostrado posteriormente nesta seção.

oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0";

ResponseType: configura o manipulador do OIDC para executar apenas o fluxo do código de autorização. Concessões implícitas e fluxos híbridos são desnecessários nesse modo. O manipulador do OIDC solicita automaticamente os tokens apropriados usando o código retornado do ponto de extremidade da autorização.

oidcOptions.ResponseType = OpenIdConnectResponseType.Code;

MapInboundClaims e a configuração de NameClaimType e RoleClaimType: muitos servidores OIDC usam "name" e "role" em vez dos padrões SOAP/WS-Fed em ClaimTypes. Quando MapInboundClaims está definido como false, o manipulador não executa mapeamentos de declarações e os nomes das declarações do JWT são usados diretamente pelo aplicativo. O exemplo a seguir define o tipo de declaração de função como "roles", que é apropriado para a ID do Microsoft Entra (ME-ID). Consulte a documentação do seu provedor de identidade para obter mais informações.

Note

MapInboundClaims deve ser definido como false para a maioria dos provedores OIDC, o que impede a renomeação de declarações.

oidcOptions.MapInboundClaims = false;
oidcOptions.TokenValidationParameters.NameClaimType = "name";
oidcOptions.TokenValidationParameters.RoleClaimType = "roles";

Configuração do caminho: os caminhos devem corresponder ao URI de redirecionamento (caminho de retorno de chamada do logon) e aos caminhos de redirecionamento de logoff (caminho de retorno de chamada de saída) configurados ao registrar o aplicativo com o provedor OIDC. No portal do Azure, os caminhos são configurados na folha Autenticação do registro do aplicativo. Os caminhos de entrada e saída devem ser registrados como URIs de redirecionamento. Os valores padrão são /signin-oidc e /signout-callback-oidc.

CallbackPath: o caminho da solicitação dentro do caminho base do aplicativo no qual o agente do usuário é retornado.

Configure o caminho de retorno de logout no registro do provedor OIDC do aplicativo. No exemplo a seguir, o placeholder {PORT} é a porta do aplicativo:

https://localhost:{PORT}/signin-oidc

Note

Uma porta não é necessária para endereços localhost ao usar o Microsoft Entra ID. A maioria dos outros provedores OIDC exige a porta correta.

SignedOutCallbackPath (chave de configuração: "SignedOutCallbackPath"): o caminho da solicitação dentro do caminho base do aplicativo, interceptado pelo manipulador OIDC, onde o agente do usuário é inicialmente redirecionado após sair do provedor de identidade. O aplicativo de exemplo não define um valor para o caminho porque o valor padrão "/signout-callback-oidc" é usado. Depois de interceptar a solicitação, o manipulador OIDC redireciona para o SignedOutRedirectUri ou RedirectUri, se especificado.

Configure o caminho de retorno de logout no registro do provedor OIDC do aplicativo. No exemplo a seguir, o placeholder {PORT} é a porta do aplicativo:

https://localhost:{PORT}/signout-callback-oidc

Note

Ao usar a ID do Microsoft Entra, defina o caminho nas entradas de URI de Redirecionamento da configuração da plataforma Web no portal do Entra ou do Azure. Uma porta não é necessária para endereços localhost ao usar o Entra. A maioria dos outros provedores OIDC exige a porta correta. Se você não adicionar o URI de caminho de retorno de chamada após desconexão ao registro do aplicativo no Entra, o Entra se recusará a redirecionar o usuário de volta para o aplicativo e apenas solicitará que ele feche a janela do navegador.

RemoteSignOutPath: as solicitações recebidas nesse caminho fazem com que o manipulador invoque a saída usando o esquema de saída.

No exemplo a seguir, o placeholder {PORT} é a porta do aplicativo:

https://localhost/signout-oidc

Note

Ao usar a ID do Microsoft Entra, defina a URL de logoff do front-channel no portal do Entra ou do Azure. Uma porta não é necessária para endereços localhost ao usar o Entra. A maioria dos outros provedores OIDC exige a porta correta.

oidcOptions.CallbackPath = new PathString("{PATH}");
oidcOptions.SignedOutCallbackPath = new PathString("{PATH}");
oidcOptions.RemoteSignOutPath = new PathString("{PATH}");

Exemplos (valores padrão):

oidcOptions.CallbackPath = new PathString("/signin-oidc");
oidcOptions.SignedOutCallbackPath = new PathString("/signout-callback-oidc");
oidcOptions.RemoteSignOutPath = new PathString("/signout-oidc");

(Microsoft Azure somente com o ponto de extremidade "comum") TokenValidationParameters.IssuerValidator: muitos provedores OIDC trabalham com o validador do emissor padrão, mas precisamos considerar o emissor parametrizado com a ID do Locatário ({TENANT ID}) retornada por https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration. Para obter mais informações, consulte SecurityTokenInvalidIssuerException com OpenID Connect e o endpoint "common" do Azure AD (AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet nº 1731).

Somente para aplicativos que usam o Microsoft Entra ID ou o Azure AD B2C com o ponto de extremidade "comum":

var microsoftIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(oidcOptions.Authority);
oidcOptions.TokenValidationParameters.IssuerValidator = microsoftIssuerValidator.Validate;

Para o Microsoft Entra ID ou Azure AD B2C, você pode usar AddMicrosoftIdentityWebApp da Microsoft Identity Web (Microsoft.Identity.Web pacote NuGet, documentação da API), que adiciona os manipuladores de OIDC e Cookie autenticação com os padrões apropriados. O aplicativo de exemplo e as diretrizes neste artigo não usam a Microsoft Identity Web. As diretrizes demonstram como configurar o manipulador OIDC manualmente para qualquer provedor OIDC. Para obter mais informações sobre como implementar a Microsoft Identity Web, consulte Proteja um ASP.NET Core Blazor Web App com Microsoft Entra ID.

Esta versão do artigo aborda a implementação do OIDC com o padrão Backend para Frontend (BFF). Se a especificação do aplicativo não chamar para a adoção do padrão BFF, altere o seletor de versão do artigo para padrão não BFF (Interativo Automático) (renderização automática interativa) ou padrão não BFF (Servidor Interativo) (renderização do Servidor Interativo).

Prerequisites

Aspire requer o Visual Studio versão 17.10 ou posterior.

Além disso, consulte a seção Pré-requisitos do Início Rápido: Criar sua primeira Aspire solução.

Solução de exemplo

O aplicativo de exemplo consiste nos seguintes projetos:

  • Aspire:
    • Aspire.AppHost: usado para gerenciar as preocupações de orquestração de alto nível do aplicativo.
    • Aspire.ServiceDefaults: contém configurações padrão do aplicativo Aspire que podem ser estendidas e personalizadas conforme necessário.
  • MinimalApiJwt: API de back-end, contendo um exemplo de endpoint Minimal API para dados meteorológicos.
  • BlazorWebAppOidc: projeto do servidor do Blazor Web App. O projeto usa YARP para fazer proxy de solicitações para um endpoint de previsão do tempo no projeto de API Web de back-end (MinimalApiJwt) com o access_token armazenado na autenticação cookie.
  • BlazorWebAppOidc.Client: projeto do cliente do Blazor Web App.

Acesse o exemplo por meio da pasta de versão mais recente no Blazor repositório de exemplos com o link a seguir. O exemplo está na BlazorWebAppOidcBff pasta para .NET 8 ou posterior.

Exibir ou baixar o código de exemplo (como baixar)

O Blazor Web App usa o modo de renderização automática com interatividade global.

O projeto do servidor chama AddAuthenticationStateSerialization para adicionar um provedor de estado de autenticação no lado do servidor que utiliza PersistentComponentState para transferir o estado de autenticação para o cliente. O cliente chama AddAuthenticationStateDeserialization para desserializar e usar o estado de autenticação passado pelo servidor. O estado de autenticação é fixado para a duração do aplicativo WebAssembly.

A classe PersistingAuthenticationStateProvider (PersistingAuthenticationStateProvider.cs) é um AuthenticationStateProvider do lado do servidor que usa PersistentComponentState para transmitir o estado de autenticação para o cliente, que é então corrigido para o tempo de vida do aplicativo WebAssembly.

Este aplicativo é um ponto de partida para qualquer fluxo de autenticação OIDC. OIDC é configurado manualmente no aplicativo e não depende do Microsoft Entra ID ou dos pacotes Microsoft Identity Web, nem o aplicativo de exemplo requer hospedagem no Microsoft Azure. No entanto, o aplicativo de exemplo pode ser usado com o Entra, Microsoft Identity Web e hospedado no Azure.

Atualização automática de token não interativo com a ajuda de um atualizador cookie personalizado (CookieOidcRefresher.cs).

O padrão Backend para Frontend (BFF) é adotado para descoberta de serviço usando Aspire e o YARP para fazer proxy de solicitações para um endpoint de previsão do tempo no aplicativo back-end.

A API Web de back-end (MinimalApiJwt) usa a autenticação de portador JWT para validar tokens JWT salvos pelo Blazor Web App no login.

Aspire melhora a experiência de criação de aplicativos nativos de nuvem do .NET. Ele fornece um conjunto consistente e opinativo de ferramentas e padrões para criar e executar aplicativos distribuídos.

O YARP (Outro Proxy Reverso) é uma biblioteca usada para criar um servidor proxy reverso. MapForwarder Program no arquivo do projeto de servidor adiciona o encaminhamento direto de solicitações HTTP que correspondem ao padrão especificado a um destino específico usando a configuração padrão para a solicitação de saída, transformações personalizadas e cliente HTTP padrão:

  • Ao renderizar o componente Weather no servidor, o componente usa a classe ServerWeatherForecaster para fazer proxy da solicitação de dados meteorológicos com o token de acesso do usuário. IHttpContextAccessor.HttpContext determina se um HttpContext está disponível para uso pelo método GetWeatherForecastAsync. Para obter mais informações, consulte ASP.NET Componentes principaisRazor.
  • Quando o componente é renderizado no cliente, o componente usa a implementação do serviço ClientWeatherForecaster, que usa um HttpClient pré-configurado (no arquivo do projeto Program do cliente) para fazer uma chamada à API Web para o projeto do servidor. Um ponto de extremidade de API Mínima (/weather-forecast) definido no arquivo do projeto Program do servidor transforma a solicitação com o token de acesso do usuário para obter os dados meteorológicos.

Para obter mais informações sobre chamadas de API (Web) usando abstrações de serviço em Blazor Web Apps, consulte Chamar uma API Web de um aplicativo ASP.NET CoreBlazor.

Registros de aplicativo do Microsoft Entra ID

É recomendável usar registros separados para aplicativos e APIs Web, mesmo quando os aplicativos e AS APIs Web estão na mesma solução. As diretrizes a seguir são para o BlazorWebAppOidc aplicativo e MinimalApiJwt a API Web da solução de exemplo, mas as mesmas diretrizes geralmente se aplicam a quaisquer registros baseados em Entra para aplicativos e APIs Web.

Registre a API Web (MinimalApiJwt) primeiro para que você possa conceder acesso à API Web ao registrar o aplicativo. As IDs do locatário e do cliente da API Web são usadas para configurar a API Web em seu arquivo Program. Depois de registrar a API Web, exponha a API Web em registros de aplicativo>Expor uma API com um nome de escopo de Weather.Get. Registre o URI da ID do Aplicativo para uso na configuração do aplicativo.

Em seguida, registre o aplicativo (BlazorWebAppOidc/BlazorWebApOidc.Client) com uma configuração de plataforma Web e um URI de Redirecionamento de https://localhost/signin-oidc (uma porta não é necessária). A ID do inquilino do aplicativo e a ID do cliente, juntamente com o endereço base da API Web, o URI da ID do App e o nome do escopo de clima, são usados para configurar o aplicativo em seu arquivo Program. Conceda permissões da API para acessar a API web em Registros de Aplicativo>Permissões da API. Se a especificação de segurança do aplicativo o chamar, você poderá conceder consentimento do administrador para que a organização acesse a API Web. Usuários e grupos autorizados são atribuídos ao registro do aplicativo em Registros de aplicativos>Aplicativos empresariais.

Na configuração de registro de aplicativo de concessão implícita e fluxos híbridos do Portal Entra ou do Azure, não selecione nenhuma das caixas de seleção para o ponto de extremidade de autorização retornar tokens de acesso ou tokens de identificação. O manipulador do OpenID Connect solicita automaticamente os tokens apropriados usando o código retornado do endpoint de autorização.

Crie um segredo do cliente no registro do aplicativo no portal do Entra ou do Azure (Gerenciar>Certificados &segredos>Novo segredo do cliente). Mantenha o segredo do cliente Valor para usar na próxima seção.

Diretrizes adicionais de configuração do Entra para configurações específicas são fornecidas posteriormente neste artigo.

Estabelecer o segredo do cliente

Esta seção se aplica apenas ao Blazor Web App projeto de servidor (BlazorWebAppOidc).

Warning

Não armazene segredos do aplicativo, cadeias de conexão, credenciais, senhas, PINs (números de identificação pessoal), código C#/.NET privado ou chaves/tokens privados no código do lado do cliente, que é sempre inseguro. Em ambientes de teste/preparo e produção, o código do lado do Blazor servidor e as APIs Web devem usar fluxos de autenticação seguros que evitam a manutenção de credenciais no código do projeto ou nos arquivos de configuração. Fora dos testes de desenvolvimento local, recomendamos evitar o uso de variáveis de ambiente para armazenar dados confidenciais, pois as variáveis de ambiente não são a abordagem mais segura. Para testes de desenvolvimento local, a ferramenta Gerenciador de Segredos é recomendada para proteger dados confidenciais. Para obter mais informações, consulte Manter com segurança dados confidenciais e credenciais.

Para testes de desenvolvimento local, use a ferramenta Gerenciador de Segredos para armazenar o Blazor segredo do cliente do projeto do servidor na chave Authentication:Schemes:MicrosoftOidc:ClientSecret de configuração.

O Blazor projeto do servidor não foi inicializado para a ferramenta do Gerenciador de Segredos. Use um shell de comando, como o shell de comando do PowerShell do Desenvolvedor no Visual Studio, para executar o comando a seguir. Antes de executar o comando, altere o diretório com o cd comando para o diretório do projeto do servidor. O comando estabelece um identificador de segredos do usuário (<UserSecretsId> no arquivo de projeto do aplicativo do servidor):

dotnet user-secrets init

Execute o comando a seguir para definir o segredo do cliente. O marcador {SECRET} é o segredo do cliente obtido do registro do aplicativo:

dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}"

Se estiver usando o Visual Studio, você poderá confirmar se o segredo está definido clicando com o botão direito do mouse no projeto do servidor no Gerenciador de Soluções e selecionando Gerenciar Segredos do Usuário.

Aspire projetos

Para obter mais informações sobre como usar Aspire e detalhes sobre os projetos .AppHost e .ServiceDefaults do aplicativo de exemplo, consulte a documentação Aspire.

Confirme se você atendeu aos pré-requisitos do Aspire. Para obter mais informações, consulte a seção Pré-requisitos do Início Rápido: Criar sua primeira Aspire solução.

O aplicativo de amostra configura apenas um perfil de inicialização HTTP inseguro (http) para uso durante o teste de desenvolvimento. Para obter mais informações, incluindo um exemplo de perfis seguros e inseguros de configurações de inicialização, consulte Permitir transporte inseguro (AspireAspiredocumentação).

MinimalApiJwt projeto

O projeto MinimalApiJwt é uma API Web de back-end para vários projetos de front-end. O projeto configura um endpoint Minimal API para dados meteorológicos. As solicitações do projeto do lado do servidor Blazor Web App (BlazorWebAppOidc) são encaminhadas por proxy para o projeto MinimalApiJwt.

O arquivo MinimalApiJwt.http pode ser usado para testar a solicitação de dados meteorológicos. Observe que o projeto MinimalApiJwt deve estar em execução para testar o ponto de extremidade e o ponto de extremidade está codificado no arquivo. Para obter mais informações, consulte Usar arquivos .http no Visual Studio 2022.

O projeto inclui pacotes e configuração para produzir documentos OpenAPI.

O projeto inclui pacotes e configuração para produzir documentos OpenAPI e a interface do usuário do Swagger no ambiente de desenvolvimento. Para obter mais informações, consulte Usar os documentos OpenAPI gerados.

Um endpoint seguro de dados de previsão do tempo está no arquivo do projeto Program.

app.MapGet("/weather-forecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
}).RequireAuthorization();

O método de extensão RequireAuthorization requer autorização para a definição de rota. Para todos os controladores que você adicionar ao projeto, adicione o [Authorize] atributo ao controlador ou à ação.

Configure o projeto na JwtBearerOptions da chamada AddJwtBearer no arquivo Program do projeto.

Configura Authority a autoridade para fazer chamadas OIDC. É recomendável usar um registro de aplicativo separado para o MinimalApiJwt projeto. A autoridade corresponde ao issurer (iss) do JWT retornado pelo provedor de identidade.

jwtOptions.Authority = "{AUTHORITY}";

O formato da Autoridade depende do tipo de locatário em uso. Os exemplos a seguir para o Microsoft Entra ID usam uma ID do Locatário de aaaabbbb-0000-cccc-1111-dddd2222eeee.

ME-ID exemplo da Autoridade do Inquilino:

jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee";

Exemplo da Autoridade de Locatário do AAD B2C:

jwtOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0";

Audience configura o público-alvo de qualquer token OIDC recebido.

jwtOptions.Audience = "{APP ID URI}";

Note

Ao usar a ID do Microsoft Entra, corresponda apenas o valor ao caminho do URI da ID do Aplicativo configurado ao adicionar o Weather.Get escopo em Expor uma API no portal do Entra ou do Azure. Não inclua o nome do escopo, "Weather.Get", no valor.

O formato do Público-Alvo depende do tipo de locatário em uso. Os exemplos a seguir de ID do Microsoft Entra usam uma ID de locatário de contoso e uma ID de cliente de 11112222-bbbb-3333-cccc-4444dddd5555.

ME-ID exemplo de URI de ID do aplicativo do locatário:

jwtOptions.Audience = "api://11112222-bbbb-3333-cccc-4444dddd5555";

Exemplo de URI da ID do aplicativo de locatário B2C do AAD:

jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555";

Projeto Blazor Web App do servidor (BlazorWebAppOidc)

Esta seção explica como configurar o projeto do lado Blazor do servidor.

A configuração a seguir OpenIdConnectOptions é encontrada no arquivo Program do projeto na chamada para AddOpenIdConnect.

PushedAuthorizationBehavior: controla o suporte a PAR (Solicitações de Autorização Enviadas por Push). Por padrão, a configuração é usar PAR se o documento de descoberta do provedor de identidade (geralmente encontrado em .well-known/openid-configuration) indicar suporte para PAR. Se você quiser exigir suporte par para o aplicativo, poderá atribuir um valor de PushedAuthorizationBehavior.Require. O PAR não é compatível com o Microsoft Entra e não há planos para o Entra dar suporte a ele no futuro.

oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.UseIfAvailable;

SignInScheme: define o esquema de autenticação correspondente ao middleware responsável por persistir a identidade do usuário após uma autenticação bem-sucedida. O manipulador do OIDC precisa usar um esquema de entrada capaz de persistir as credenciais do usuário entre solicitações. A linha a seguir está presente apenas para fins de demonstração. Se omitido, DefaultSignInScheme será usado como um valor de fallback.

oidcOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;

Escopos para openid e profile (Scope) (Opcional): os escopos openid e profile também são configurados por padrão porque são necessários para que o manipulador do OIDC funcione, mas eles podem precisar ser adicionados novamente se os escopos forem incluídos na configuração Authentication:Schemes:MicrosoftOidc:Scope. Para obter diretrizes gerais de configuração, consulte Configuração no ASP.NET Core e Configuração do ASP.NET CoreBlazor.

oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);

SaveTokens: define se os tokens de acesso e atualização devem ser armazenados no AuthenticationProperties após uma autorização bem-sucedida. O valor é definido como true para autenticar solicitações de dados meteorológicos do projeto de API Web de back-end (MinimalApiJwt).

oidcOptions.SaveTokens = true;

Escopo para acesso offline (Scope): o escopo offline_access é necessário para o token de atualização.

oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);

Escopos para obter dados meteorológicos da API Web (Scope): configure o Weather.Get escopo para acessar a API Web externa para dados meteorológicos. No exemplo a seguir, o marcador de posição {APP ID URI} é encontrado no portal do Entra ou do Azure onde a API Web é exposta. Para qualquer outro provedor de identidade, use o escopo apropriado.

oidcOptions.Scope.Add("{APP ID URI}/Weather.Get");

O formato do escopo depende do tipo de locatário em uso. Nos exemplos a seguir, o Domínio do Locatário é contoso.onmicrosoft.come a ID do cliente é 11112222-bbbb-3333-cccc-4444dddd5555.

ME-ID exemplo de URI de ID do aplicativo do locatário:

oidcOptions.Scope.Add("api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get");

Exemplo de URI da ID do aplicativo de locatário B2C do AAD:

oidcOptions.Scope.Add("https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get");

Authority e ClientId: define a autoridade e a ID do cliente para chamadas ao OIDC.

oidcOptions.Authority = "{AUTHORITY}";
oidcOptions.ClientId = "{CLIENT ID}";

O exemplo a seguir usa um ID de locatário aaaabbbb-0000-cccc-1111-dddd2222eeee e um ID de cliente 00001111-aaaa-2222-bbbb-3333cccc4444:

oidcOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0";
oidcOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";

Para aplicativos multilocatários, a autoridade "comum" deve ser usada. Você também pode usar a autoridade "comum" para aplicativos de locatário único, mas um IssuerValidator personalizado é necessário, conforme será mostrado posteriormente nesta seção.

oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0";

ResponseType: configura o manipulador do OIDC para executar apenas o fluxo do código de autorização. Concessões implícitas e fluxos híbridos são desnecessários nesse modo. O manipulador do OIDC solicita automaticamente os tokens apropriados usando o código retornado do ponto de extremidade da autorização.

oidcOptions.ResponseType = OpenIdConnectResponseType.Code;

MapInboundClaims e a configuração de NameClaimType e RoleClaimType: muitos servidores OIDC usam "name" e "role" em vez dos padrões SOAP/WS-Fed em ClaimTypes. Quando MapInboundClaims está definido como false, o manipulador não executa mapeamentos de declarações e os nomes das declarações do JWT são usados diretamente pelo aplicativo. O exemplo a seguir define o tipo de declaração de função como "roles", que é apropriado para a ID do Microsoft Entra (ME-ID). Consulte a documentação do seu provedor de identidade para obter mais informações.

Note

MapInboundClaims deve ser definido como false para a maioria dos provedores OIDC, o que impede a renomeação de declarações.

oidcOptions.MapInboundClaims = false;
oidcOptions.TokenValidationParameters.NameClaimType = "name";
oidcOptions.TokenValidationParameters.RoleClaimType = "roles";

Configuração do caminho: os caminhos devem corresponder ao URI de redirecionamento (caminho de retorno de chamada do logon) e aos caminhos de redirecionamento de logoff (caminho de retorno de chamada de saída) configurados ao registrar o aplicativo com o provedor OIDC. No portal do Azure, os caminhos são configurados na folha Autenticação do registro do aplicativo. Os caminhos de entrada e saída devem ser registrados como URIs de redirecionamento. Os valores padrão são /signin-oidc e /signout-callback-oidc.

Configure o caminho de retorno de logout no registro do provedor OIDC do aplicativo. No exemplo a seguir, o placeholder {PORT} é a porta do aplicativo:

https://localhost:{PORT}/signin-oidc

Note

Uma porta não é necessária para endereços localhost ao usar o Microsoft Entra ID. A maioria dos outros provedores OIDC exige a porta correta.

SignedOutCallbackPath (chave de configuração: "SignedOutCallbackPath"): o caminho da solicitação dentro do caminho base do aplicativo, interceptado pelo manipulador OIDC, onde o agente do usuário é inicialmente redirecionado após sair do provedor de identidade. O aplicativo de exemplo não define um valor para o caminho porque o valor padrão "/signout-callback-oidc" é usado. Depois de interceptar a solicitação, o manipulador OIDC redireciona para o SignedOutRedirectUri ou RedirectUri, se especificado.

Configure o caminho de retorno de logout no registro do provedor OIDC do aplicativo. No exemplo a seguir, o placeholder {PORT} é a porta do aplicativo:

https://localhost:{PORT}/signout-callback-oidc

Note

Ao usar a ID do Microsoft Entra, defina o caminho nas entradas de URI de Redirecionamento da configuração da plataforma Web no portal do Entra ou do Azure. Uma porta não é necessária para endereços localhost ao usar o Entra. A maioria dos outros provedores OIDC exige a porta correta. Se você não adicionar o URI de caminho de retorno de chamada após desconexão ao registro do aplicativo no Entra, o Entra se recusará a redirecionar o usuário de volta para o aplicativo e apenas solicitará que ele feche a janela do navegador.

RemoteSignOutPath: as solicitações recebidas nesse caminho fazem com que o manipulador invoque a saída usando o esquema de saída.

No exemplo a seguir, o placeholder {PORT} é a porta do aplicativo:

https://localhost/signout-oidc

Note

Ao usar a ID do Microsoft Entra, defina a URL de logoff do front-channel no portal do Entra ou do Azure. Uma porta não é necessária para endereços localhost ao usar o Entra. A maioria dos outros provedores OIDC exige a porta correta.

oidcOptions.CallbackPath = new PathString("{PATH}");
oidcOptions.SignedOutCallbackPath = new PathString("{PATH}");
oidcOptions.RemoteSignOutPath = new PathString("{PATH}");

Exemplos (valores padrão):

oidcOptions.CallbackPath = new PathString("/signin-oidc");
oidcOptions.SignedOutCallbackPath = new PathString("/signout-callback-oidc");
oidcOptions.RemoteSignOutPath = new PathString("/signout-oidc");

(Microsoft Azure somente com o ponto de extremidade "comum") TokenValidationParameters.IssuerValidator: muitos provedores OIDC trabalham com o validador do emissor padrão, mas precisamos considerar o emissor parametrizado com a ID do Locatário ({TENANT ID}) retornada por https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration. Para obter mais informações, consulte SecurityTokenInvalidIssuerException com OpenID Connect e o endpoint "common" do Azure AD (AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet nº 1731).

Somente para aplicativos que usam o Microsoft Entra ID ou o Azure AD B2C com o ponto de extremidade "comum":

var microsoftIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(oidcOptions.Authority);
oidcOptions.TokenValidationParameters.IssuerValidator = microsoftIssuerValidator.Validate;

Projeto Blazor Web App do cliente (BlazorWebAppOidc.Client)

O projeto BlazorWebAppOidc.Client é o projeto cliente do Blazor Web App.

O cliente chama AddAuthenticationStateDeserialization para desserializar e usar o estado de autenticação passado pelo servidor. O estado de autenticação é fixado para a duração do aplicativo WebAssembly.

A classe PersistentAuthenticationStateProvider (PersistentAuthenticationStateProvider.cs) é uma AuthenticationStateProvider do lado do cliente que determina o estado de autenticação do usuário procurando dados persistidos na página quando ele foi renderizado no servidor. O estado de autenticação é fixado para a duração do aplicativo WebAssembly.

Se o usuário precisar fazer logon ou logoff, será necessário recarregar a página inteira.

O aplicativo de exemplo fornece apenas um nome de usuário e email para fins de exibição.

Serializar apenas as declarações de nome e função

Esta seção se aplica apenas ao padrão não BFF (Interativo Automático) e ao padrão BFF (Interativo Automático) e seus aplicativos de exemplo.

No arquivo Program, todas as reivindicações são serializadas ao definir SerializeAllClaims para true. Se você quiser apenas as declarações de nome e função serializadas para CSR, remova a opção ou defina-a como false.

Fornecer configuração com o provedor de configuração JSON (configurações do aplicativo)

Os projetos de solução de exemplo configuram a autenticação de portadores OIDC e JWT em seus Program arquivos para tornar as configurações detectáveis usando o preenchimento automático de C#. Os aplicativos profissionais geralmente usam um provedor de configuração para configurar opções OIDC, como o provedor de configuração JSON padrão. O provedor de configuração JSON carrega a configuração de arquivos de configurações do aplicativo appsettings.json/appsettings.{ENVIRONMENT}.json, onde o espaço reservado {ENVIRONMENT} é o ambiente de execução do aplicativo. Siga as diretrizes nesta seção para usar arquivos de configurações de aplicativo para configuração.

No arquivo de configurações do aplicativo (appsettings.json) do projeto BlazorWebAppOidc ou BlazorWebAppOidcServer, adicione a seguinte configuração JSON:

"Authentication": {
  "Schemes": {
    "MicrosoftOidc": {
      "Authority": "https://login.microsoftonline.com/{TENANT ID (BLAZOR APP)}/v2.0",
      "ClientId": "{CLIENT ID (BLAZOR APP)}",
      "CallbackPath": "/signin-oidc",
      "SignedOutCallbackPath": "/signout-callback-oidc",
      "RemoteSignOutPath": "/signout-oidc",
      "SignedOutRedirectUri": "/",
      "Scope": [
        "openid",
        "profile",
        "offline_access",
        "{APP ID URI (WEB API)}/Weather.Get"
      ]
    }
  }
},

Atualize os espaços reservados na configuração anterior para corresponder aos valores que o aplicativo usa no Program arquivo:

  • {TENANT ID (BLAZOR APP)}: a ID do locatário do Blazor aplicativo.
  • {CLIENT ID (BLAZOR APP)}: a ID do cliente do Blazor aplicativo.
  • {APP ID URI (WEB API)}: o URI da ID do aplicativo da API Web.

A Autoridade "comum" (https://login.microsoftonline.com/common/v2.0) deve ser usada para aplicativos multilocatários. Para usar a Autoridade "comum" para aplicativos de locatário único, consulte a seção Usar a Autoridade "comum" para aplicativos de locatário único .

Atualize quaisquer outros valores na configuração anterior para corresponder a valores personalizados/não padrão usados no Program arquivo.

A configuração é automaticamente reconhecida pelo construtor de autenticação.

Remova as seguintes linhas do Program arquivo:

- oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);
- oidcOptions.Scope.Add("...");
- oidcOptions.CallbackPath = new PathString("...");
- oidcOptions.SignedOutCallbackPath = new PathString("...");
- oidcOptions.RemoteSignOutPath = new PathString("...");
- oidcOptions.Authority = "...";
- oidcOptions.ClientId = "...";

ConfigureCookieOidc No método de CookieOidcServiceCollectionExtensions.cs, remova a seguinte linha:

- oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);

No projeto MinimalApiJwt, adicione as seguintes configurações de aplicativo ao arquivo appsettings.json.

"Authentication": {
  "Schemes": {
    "Bearer": {
      "Authority": "https://sts.windows.net/{TENANT ID (WEB API)}",
      "ValidAudiences": [ "{APP ID URI (WEB API)}" ]
    }
  }
},

Atualize os espaços reservados na configuração anterior para corresponder aos valores que o aplicativo usa no Program arquivo:

  • {TENANT ID (WEB API)}: a ID do locatário da API Web.
  • {APP ID URI (WEB API)}: o URI da ID do aplicativo da API Web.

Os formatos de autoridade adotam os seguintes padrões:

  • ME-ID tipo de locatário: https://sts.windows.net/{TENANT ID}
  • ID Externo do Microsoft Entra: https://{DIRECTORY NAME}.ciamlogin.com/{TENANT ID}/v2.0
  • Tipo de locatário B2C: https://login.microsoftonline.com/{TENANT ID}/v2.0

Os formatos de audiência adotam os seguintes padrões ({CLIENT ID} é a ID do cliente da API Web; {DIRECTORY NAME} é o nome do diretório, por exemplo, contoso):

  • ME-ID tipo de locatário: api://{CLIENT ID}
  • ID Externa do Microsoft Entra: {CLIENT ID}
  • Tipo de locatário B2C: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID}

A configuração é automaticamente captada pelo construtor de autenticação de portador JWT.

Remova as seguintes linhas do Program arquivo:

- jwtOptions.Authority = "...";
- jwtOptions.Audience = "...";

Para obter mais informações sobre a configuração, consulte os seguintes recursos:

Usar a autoridade comum para aplicativos de locatário único.

Você pode usar a Autoridade "comum" para aplicativos de um único locatário, mas deve seguir estas etapas para implementar um validador de emissor personalizado.

Adicione o Microsoft.IdentityModel.Validators pacote NuGet ao BlazorWebAppOidc, BlazorWebAppOidcServer ou BlazorWebAppOidcBff projeto.

Note

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.

Na parte superior do Program arquivo, disponibilize o Microsoft.IdentityModel.Validators namespace:

using Microsoft.IdentityModel.Validators;

Use o seguinte código no arquivo em que as Program opções OIDC estão configuradas:

var microsoftIssuerValidator = 
    AadIssuerValidator.GetAadIssuerValidator(oidcOptions.Authority);
oidcOptions.TokenValidationParameters.IssuerValidator = 
    microsoftIssuerValidator.Validate;

Para obter mais informações, consulte SecurityTokenInvalidIssuerException com OpenID Connect e o endpoint "common" do Azure AD (AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet nº 1731).

Redirecionar para a página inicial ao sair

O componente LogInOrOut (Layout/LogInOrOut.razor) define um campo oculto para a URL de retorno (ReturnUrl) para a URL atual (currentURL). Quando o usuário sai do aplicativo, o provedor de identidade retorna o usuário para a página da qual ele fez logon. Se o usuário sair de uma página segura, ele será retornado para a mesma página segura e enviado de volta por meio do processo de autenticação. Esse fluxo de autenticação é razoável quando os usuários precisam alterar contas regularmente.

Como alternativa, use o seguinte componente LogInOrOut, que não fornece uma URL de retorno ao fazer logout.

Layout/LogInOrOut.razor:

<div class="nav-item px-3">
    <AuthorizeView>
        <Authorized>
            <form action="authentication/logout" method="post">
                <AntiforgeryToken />
                <button type="submit" class="nav-link">
                    <span class="bi bi-arrow-bar-left-nav-menu" aria-hidden="true">
                    </span> Logout
                </button>
            </form>
        </Authorized>
        <NotAuthorized>
            <a class="nav-link" href="authentication/login">
                <span class="bi bi-person-badge-nav-menu" aria-hidden="true"></span> 
                Login
            </a>
        </NotAuthorized>
    </AuthorizeView>
</div>

Atualização de token

A implementação do cookie atualizador (CookieOidcRefresher.cs) personalizado atualiza as declarações do usuário automaticamente quando elas expiram. A implementação atual espera receber um token de ID do ponto de extremidade do token em troca do token de atualização. As declarações neste token de ID são então usadas para sobrescrever as declarações do usuário.

A implementação de exemplo não inclui código para solicitar informações do endpoint UserInfo na atualização do token. Para obter mais informações, consulte BlazorWebAppOidc AddOpenIdConnect with GetClaimsFromUserInfoEndpoint = true doesn't propogate [sic] role claims to client (dotnet/aspnetcore nº 58826).

Note

Alguns provedores de identidade só retornam um token de acesso quando usam um token de atualização. O CookieOidcRefresher pode ser atualizado com lógica adicional para continuar a usar o conjunto anterior de declarações armazenadas na autenticação cookie ou usar o token de acesso para solicitar declarações do endpoint UserInfo.

Nonce criptográfico

Um nonce é um valor de cadeia de caracteres que associa a sessão de um cliente a um token de ID para atenuar ataques de reprodução.

Se você receber um erro de nonce durante o desenvolvimento e teste de autenticação, use uma nova sessão do navegador InPrivate/incógnito para cada execução de teste, não importa o tamanho da alteração feita no aplicativo ou no usuário de teste, pois os dados obsoletos de cookie podem resultar em um erro de nonce. Para obter mais informações, consulte a seção Cookies e dados do site .

Um nonce não é necessário ou usado quando um token de atualização é trocado por um novo token de acesso. No aplicativo de exemplo, o CookieOidcRefresher (CookieOidcRefresher.cs) define deliberadamente OpenIdConnectProtocolValidator.RequireNonce como false.

Funções de aplicativo para aplicativos não registrados com o Microsoft Entra (ME-ID)

Esta seção refere-se a aplicativos que não usam do Microsoft Entra ID (ME-ID) como o provedor de identidade. Para aplicativos registrados com a ID de ME, consulte a seção Funções de aplicativo para aplicativos registrados no Microsoft Entra (ME-ID).

Configure o tipo de declaração de função (TokenValidationParameters.RoleClaimType) no OpenIdConnectOptions de Program.cs:

oidcOptions.TokenValidationParameters.RoleClaimType = "{ROLE CLAIM TYPE}";

Para muitos provedores de identidade OIDC, o tipo de declaração de função é role. Verifique a documentação do provedor de identidade para obter o valor correto.

Substitua a classe UserInfo no projeto BlazorWebAppOidc.Client pela classe a seguir.

UserInfo.cs:

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

namespace BlazorWebAppOidc.Client;

// Add properties to this class and update the server and client 
// AuthenticationStateProviders to expose more information about 
// the authenticated user to the client.
public sealed class UserInfo
{
    public required string UserId { get; init; }
    public required string Name { get; init; }
    public required string[] Roles { get; init; }

    public const string UserIdClaimType = "sub";
    public const string NameClaimType = "name";
    private const string RoleClaimType = "role";

    public static UserInfo FromClaimsPrincipal(ClaimsPrincipal principal) =>
        new()
        {
            UserId = GetRequiredClaim(principal, UserIdClaimType),
            Name = GetRequiredClaim(principal, NameClaimType),
            Roles = principal.FindAll(RoleClaimType).Select(c => c.Value)
                .ToArray(),
        };

    public ClaimsPrincipal ToClaimsPrincipal() =>
        new(new ClaimsIdentity(
            Roles.Select(role => new Claim(RoleClaimType, role))
                .Concat([
                    new Claim(UserIdClaimType, UserId),
                    new Claim(NameClaimType, Name),
                ]),
            authenticationType: nameof(UserInfo),
            nameType: NameClaimType,
            roleType: RoleClaimType));

    private static string GetRequiredClaim(ClaimsPrincipal principal,
        string claimType) =>
            principal.FindFirst(claimType)?.Value ??
            throw new InvalidOperationException(
                $"Could not find required '{claimType}' claim.");
}

Neste ponto, Razor os componentes podem adotar autorização baseada em função e baseada em política. As funções de aplicativo aparecem em declarações role, uma declaração por função.

Funções de aplicativo para aplicativos registrados com o Microsoft Entra (ME-ID)

Use as diretrizes nesta seção para implementar funções de aplicativo, ME-ID grupos de segurança e ME-ID funções de administrador internas para aplicativos usando a ID do Microsoft Entra (ME-ID).

A abordagem descrita nesta seção configura o ME-ID para enviar grupos e funções no cabeçalho de autenticação cookie. Quando os usuários são apenas membros de alguns grupos de segurança e funções, a abordagem a seguir deve funcionar para a maioria das plataformas de hospedagem sem encontrar um problema em que os cabeçalhos são muito longos, por exemplo, com a hospedagem do IIS que tem um limite de comprimento de cabeçalho padrão de 16 KB (MaxRequestBytes). Se o comprimento do cabeçalho for um problema devido à alta associação a grupos ou funções, recomendamos não seguir as orientações nesta seção em favor de implementar o Microsoft Graph para obter separadamente os grupos e funções de um usuário de ME-ID, uma abordagem que não aumenta o tamanho da autenticação cookie. Para obter mais informações, consulte Solicitação Incorreta – Solicitação Muito Longa – Servidor IIS (dotnet/aspnetcore nº 57545).

Configure o tipo de declaração de função (TokenValidationParameters.RoleClaimType) em OpenIdConnectOptions de Program.cs. Defina o valor como roles:

oidcOptions.TokenValidationParameters.RoleClaimType = "roles";

Embora você não possa atribuir funções a grupos sem uma conta ME-ID Premium, você pode atribuir funções aos usuários e receber declarações de função para usuários com uma conta padrão do Azure. As diretrizes nesta seção não exigem uma conta Premium do ME-ID.

Ao trabalhar com o diretório padrão, siga as diretrizes em Adicionar funções de aplicativo ao aplicativo e receba-as no token (ME-ID documentação) para configurar e atribuir funções. Se você não estiver trabalhando com o diretório padrão, edite o manifesto do aplicativo no portal do Azure para estabelecer as funções do aplicativo manualmente na entrada appRoles do arquivo de manifesto. Para obter mais informações, consulte Configurar a declaração de função (ME-ID documentação).

Os grupos de segurança do Azure de um usuário chegam em declarações groups, e as atribuições de função de administrador internas de ME-ID de um usuário chegam em declarações de IDs bem conhecidas (wids). Os valores para ambos os tipos de declaração são GUIDs. Quando recebidas pelo aplicativo, essas declarações podem ser usadas para estabelecer a função e a autorização de política em Razor componentes.

No manifesto do aplicativo no portal do Azure, defina o groupMembershipClaims atributo como All. Um valor de All faz com que o ME-ID envie todos os grupos de segurança/distribuição (declarações groups) e funções (declarações wids) do usuário que entrou. Para definir o atributo groupMembershipClaims, faça o seguinte:

  1. Abra o registro do aplicativo no portal do Azure.
  2. Selecione Gerenciar>Manifesto na barra lateral.
  3. Localize o atributo groupMembershipClaims.
  4. Defina o valor como All ("groupMembershipClaims": "All").
  5. Selecione o botão Salvar .

Substitua a classe UserInfo no projeto BlazorWebAppOidc.Client pela classe a seguir.

UserInfo.cs:

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

namespace BlazorWebAppOidc.Client;

// Add properties to this class and update the server and client 
// AuthenticationStateProviders to expose more information about 
// the authenticated user to the client.
public sealed class UserInfo
{
    public required string UserId { get; init; }
    public required string Name { get; init; }
    public required string[] Roles { get; init; }
    public required string[] Groups { get; init; }
    public required string[] Wids { get; init; }

    public const string UserIdClaimType = "sub";
    public const string NameClaimType = "name";
    private const string RoleClaimType = "roles";
    private const string GroupsClaimType = "groups";
    private const string WidsClaimType = "wids";

    public static UserInfo FromClaimsPrincipal(ClaimsPrincipal principal) =>
        new()
        {
            UserId = GetRequiredClaim(principal, UserIdClaimType),
            Name = GetRequiredClaim(principal, NameClaimType),
            Roles = principal.FindAll(RoleClaimType).Select(c => c.Value)
                .ToArray(),
            Groups = principal.FindAll(GroupsClaimType).Select(c => c.Value)
                .ToArray(),
            Wids = principal.FindAll(WidsClaimType).Select(c => c.Value)
                .ToArray(),
        };

    public ClaimsPrincipal ToClaimsPrincipal() =>
        new(new ClaimsIdentity(
            Roles.Select(role => new Claim(RoleClaimType, role))
                .Concat(Groups.Select(role => new Claim(GroupsClaimType, role)))
                .Concat(Wids.Select(role => new Claim(WidsClaimType, role)))
                .Concat([
                    new Claim(UserIdClaimType, UserId),
                    new Claim(NameClaimType, Name),
                ]),
            authenticationType: nameof(UserInfo),
            nameType: NameClaimType,
            roleType: RoleClaimType));

    private static string GetRequiredClaim(ClaimsPrincipal principal,
        string claimType) =>
            principal.FindFirst(claimType)?.Value ??
            throw new InvalidOperationException(
                $"Could not find required '{claimType}' claim.");
}

Neste ponto, Razor os componentes podem adotar autorização baseada em função e baseada em política:

  • As funções de aplicativo aparecem em declarações roles, uma declaração por função.
  • Os grupos de segurança aparecem em reivindicações groups, uma reivindicação por grupo. Os GUIDs do grupo de segurança aparecem no portal do Azure quando você cria um grupo de segurança e são listados ao selecionar Identity>Visão Geral>Grupos>Exibição.
  • As funções de administrador de ME-ID internas aparecem em declarações wids, uma declaração por função. A declaração wids com um valor de b79fbf4d-3ef9-4689-8143-76b194e85509 é sempre enviada pelo ME-ID para contas não convidadas do locatário e não se refere a uma função de administrador. GuiDs de função de administrador (IDs de modelo de função) aparecem no portal do Azure ao selecionar Funções & admins, seguido pelas reticências (...) em > da função listada. As IDs do modelo de função também estão listadas em funções internas do Microsoft Entra (documentação do Entra).

Solução: Gerenciamento de Token de Acesso do Duende

No aplicativo de exemplo, uma implementação personalizada de atualizador cookie (CookieOidcRefresher.cs) é usada para realizar a atualização automática de tokens de forma não interativa. Uma solução alternativa pode ser encontrada no pacote de software Duende.AccessTokenManagement.OpenIdConnectlivre.

O Gerenciamento de Tokens de Acesso do Duende fornece recursos automáticos de gerenciamento de tokens de acesso para aplicativos de trabalho e Web do .NET e ASP.NET Core, incluindo Blazor, sem a necessidade de adicionar um atualizador personalizado cookie.

Depois que o pacote for instalado, remova o CookieOidcRefresher e adicione o gerenciamento de token de acesso para o usuário atualmente logado no arquivo Program.

// Add services for token management
builder.Services.AddOpenIdConnectAccessTokenManagement();

// Register a typed HTTP client with token management support
builder.Services.AddHttpClient<InvoiceClient>(client =>
    {
        client.BaseAddress = new Uri("https://api.example.com/invoices/");
    })
    .AddUserAccessTokenHandler();

O cliente HTTP tipado (ou cliente HTTP nomeado, se implementado) tem gerenciamento automático de tempo de vida do token de acesso em nome do usuário conectado no momento, incluindo o gerenciamento transparente de token de atualização.

Para mais informações, consulte a documentação sobre o Gerenciamento de Tokens de Acesso Duende para .

Troubleshoot

Logging

O aplicativo de servidor é um aplicativo ASP.NET Core padrão. Consulte as diretrizes de log do ASP.NET Core para habilitar um nível de registro em log mais baixo no aplicativo do servidor.

Para habilitar o registro de log de depuração ou rastreamento para a autenticação Blazor WebAssembly, consulte a seção Log de autenticação do lado do cliente em Registro de log do ASP.NET Core Blazor configurando o seletor de versão do artigo para ASP.NET Core no .NET 7 ou posterior.

Erros comuns

  • O depurador interrompe com uma exceção durante o logoff com o Microsoft Entra External ID

    A seguinte exceção interrompe o depurador do Visual Studio durante a saída com ID Externa do Microsoft Entra:

    Uncaught TypeError TypeError: Failed to execute 'postMessage' on 'Window': The provided value cannot be converted to a sequence.

    Falha do depurador do Visual Studio na exceção do JavaScript durante o logoff

    A exceção é gerada do código JavaScript do Entra, portanto, isso não é um problema com ASP.NET Core. A exceção não afeta a funcionalidade do aplicativo na produção, portanto, a exceção pode ser ignorada durante o teste de desenvolvimento local.

  • Configuração incorreta do aplicativo ou provedor Identity (IP)

    Os erros mais comuns são causados pela configuração incorreta. A seguir, estão alguns exemplos:

    • Dependendo dos requisitos do cenário, uma Autoridade, Instância, ID do Locatário, Domínio do Locatário, ID do Cliente ou URI de Redirecionamento ausente ou incorreto impede um aplicativo autenticar clientes.
    • Escopos de solicitação incorretos impedem que os clientes acessem pontos de extremidade da API Web do servidor.
    • Permissões incorretas ou ausentes da API do servidor impedem que os clientes acessem os endpoints da API web do servidor.
    • Executar o aplicativo em uma porta diferente da configurada no URI de Redirecionamento do registro de aplicativo do IP. Observe que uma porta não é necessária para o Microsoft Entra ID e um aplicativo em execução em um endereço de teste de desenvolvimento localhost, mas a configuração da porta do aplicativo e a porta em que o aplicativo está sendo executado devem corresponder a endereços que não sejam localhost.

    A cobertura de configuração neste artigo mostra exemplos da configuração correta. Verifique cuidadosamente a configuração em busca de alguma configuração incorreta de aplicativo e IP.

    Se a configuração aparecer correta:

    • Analisar logs de aplicativos.

    • Examine o tráfego de rede entre o aplicativo cliente e o aplicativo IP ou servidor com as ferramentas de desenvolvedor do navegador. Muitas vezes, uma mensagem de erro exata ou uma mensagem com uma pista do que está causando o problema é retornada ao cliente pelo aplicativo IP ou servidor depois de fazer uma solicitação. As diretrizes das ferramentas de desenvolvedor são encontradas nos seguintes artigos:

    A equipe de documentação responde a comentários de documentos e bugs em artigos (abra um problema na seção Comentários desta página ), mas não pode fornecer suporte ao produto. Vários fóruns de suporte público estão disponíveis para ajudar na solução de problemas de um aplicativo. Recomendamos o seguinte:

    Os fóruns anteriores não pertencem ou são controlados pela Microsoft.

    Para relatórios de bugs de estrutura reproduzível não relacionados à segurança, não sensíveis e não confidenciais, registre um problema com a unidade de produto do ASP.NET Core. Não abra um problema com a unidade do produto até que você investigue completamente a causa de um problema e não possa resolvê-lo por conta própria e com a ajuda da comunidade em um fórum de suporte público. A unidade do produto não é capaz de solucionar problemas de aplicativos individuais que estão não estão funcionando devido a uma simples configuração incorreta ou casos de uso envolvendo serviços de terceiros. Se um relatório for sensível ou confidencial por natureza ou descrever uma possível falha de segurança no produto que os cibercriminosos possam explorar, consulte Relatar problemas e bugs de segurança (dotnet/aspnetcore repositório GitHub).

  • Cliente não autorizado para o ME-ID

    informação: falha na autorização do Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]. Esses requisitos não foram atendidos: DenyAnonymousAuthorizationRequirement: requer um usuário autenticado.

    Erro de retorno de chamada de logon do ME-ID:

    • Erro: unauthorized_client
    • Descrição: AADB2C90058: The provided application is not configured to allow public clients.

    Para resolver o erro:

    1. No portal do Azure, acesse o manifesto do aplicativo.
    2. Defina o allowPublicClient atributo como null ou true.

Cookies e dados do site

Cookies e dados do site podem persistir nas atualizações do aplicativo e interferir em testes e solução de problemas. Desmarque o seguinte ao fazer alterações no código do aplicativo, alterações na conta de usuário com o provedor ou alterações na configuração do aplicativo do provedor:

  • Cookies de login do usuário
  • Cookies de aplicativo
  • Dados do site armazenados e em cache

Uma abordagem para impedir que cookies e dados do site persistentes interfiram no teste e na solução de problemas é:

  • Configurar um navegador
    • Use um navegador para testes em que você pode configurar a exclusão de todos os dados de cookie e sites toda vez que o navegador for fechado.
    • Verifique se o navegador está fechado manualmente ou pelo IDE para qualquer alteração no aplicativo, usuário de teste ou configuração do provedor.
  • Use um comando personalizado para abrir um navegador no modo InPrivate ou Incógnito no Visual Studio:
    • Abra a caixa de diálogo Procurar com a partir do botão Executar no Visual Studio.
    • Selecione o botão Adicionar .
    • Forneça o caminho para o navegador no campo Programa . Os seguintes caminhos executáveis são locais de instalação típicos para Windows 10. Se o navegador estiver instalado em um local diferente ou você não estiver usando Windows 10, forneça o caminho para o executável do navegador.
      • Microsoft Edge: C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
      • Google Chrome: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
      • Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
    • No campo Argumentos , forneça a opção de linha de comando que o navegador usa para abrir no modo InPrivate ou Incógnito. Alguns navegadores exigem a URL do aplicativo.
      • Microsoft Edge: use -inprivate.
      • Google Chrome: Use --incognito --new-window {URL}, onde o espaço reservado {URL} é a URL a ser aberta (por exemplo, https://localhost:5001).
      • Mozilla Firefox: Use -private -url {URL}, onde o {URL} placeholder é a URL a ser aberta (por exemplo, https://localhost:5001).
    • Forneça um nome no campo Nome amigável . Por exemplo, Firefox Auth Testing.
    • Selecione o botão OK .
    • Para evitar a necessidade de selecionar o perfil do navegador para cada iteração de teste com um aplicativo, defina o perfil como o padrão com o botão Definir como Padrão .
    • Verifique se o navegador está fechado pelo IDE para qualquer alteração no aplicativo, usuário de teste ou configuração do provedor.

Atualizações de aplicativo

Um aplicativo em funcionamento pode falhar imediatamente depois de atualizar o SDK do .NET no computador de desenvolvimento ou alterar as versões do pacote dentro do aplicativo. Em alguns casos, pacotes incoerentes podem interromper um aplicativo ao executar atualizações principais. A maioria desses problemas pode ser corrigida seguindo estas instruções:

  1. Limpe os caches do pacote NuGet do sistema local executando dotnet nuget locals all --clear de um shell de comando.
  2. Exclua as pastas bin e obj do projeto.
  3. Restaure e reconstrua o projeto.
  4. Exclua todos os arquivos na pasta de implantação no servidor antes de reimplantar o aplicativo.

Note

Não há suporte para o uso de versões de pacote incompatíveis com a estrutura de destino do aplicativo. Para obter informações sobre um pacote, use a Galeria do NuGet.

Iniciar a solução a partir do projeto correto

Blazor Web Apps:

  • Para um dos exemplos de padrão BFF (Backend-for-Frontend), inicie a solução a partir do Aspire/Aspire.AppHost projeto.
  • Para um dos exemplos de padrão não BFF, inicie a solução a partir do projeto do servidor.

Blazor Server:

Inicie a solução no projeto do servidor.

Inspecionar o usuário

O componente UserClaims a seguir pode ser usado diretamente em aplicativos ou servir como base para personalização adicional.

UserClaims.razor:

@page "/user-claims"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]

<PageTitle>User Claims</PageTitle>

<h1>User Claims</h1>

@if (claims.Any())
{
    <ul>
        @foreach (var claim in claims)
        {
            <li><b>@claim.Type:</b> @claim.Value</li>
        }
    </ul>
}

@code {
    private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

    [CascadingParameter]
    private Task<AuthenticationState>? AuthState { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (AuthState == null)
        {
            return;
        }

        var authState = await AuthState;
        claims = authState.User.Claims;
    }
}

Recursos adicionais