Compartilhar via


Proteger um ASP.NET Core Blazor Web App com a ID do Microsoft Entra

Este artigo descreve como proteger um Blazor Web App com Plataforma de identidade da Microsoft com os pacotes Web da Microsoft Identity para Microsoft Entra ID usando um aplicativo de exemplo.

Esta versão do artigo aborda a implementação do Entra sem a adoção do padrão Back-end para Front-end (BFF). 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 é abordada:

  • O Blazor Web App usa o modo de renderização automática com interatividade global (InteractiveAuto).
  • O projeto do servidor chama AddAuthenticationStateSerialization para adicionar um provedor de estado de autenticação do lado do servidor que usa PersistentComponentState para transmitir 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 é fixo durante a duração do aplicativo WebAssembly.
  • O aplicativo usa Microsoft Entra ID, com base nos pacotes Microsoft Identity Web.
  • A atualização automática de token não interativa é gerenciada pela estrutura.
  • O aplicativo usa abstrações de serviço do lado do servidor e do lado do cliente para exibir dados meteorológicos gerados:
    • Ao renderizar o Weather componente no servidor para exibir dados meteorológicos, o componente usa o ServerWeatherForecaster. Os pacotes Web da Microsoft Identity fornecem a API para criar um serviço Web downstream nomeado para fazer chamadas à API Web. IDownstreamApi é injetado no ServerWeatherForecaster, que é usado para chamar CallApiForUserAsync para obter dados meteorológicos de uma API web externa (de um projeto MinimalApiJwt).
    • Quando o componente Weather é renderizado no cliente, o componente usa a implementação do serviço ClientWeatherForecaster, que utiliza uma HttpClient pré-configurada (no arquivo Program do projeto cliente) para fazer uma chamada de API web à API mínima do projeto servidor (/weather-forecast) para obter dados sobre o clima. O endpoint da API Minimal obtém os dados meteorológicos da ServerWeatherForecaster classe e os retorna ao cliente para renderização pelo componente.

Solução de exemplo

A solução de exemplo consiste nos seguintes projetos:

  • BlazorWebAppEntra: projeto do lado do servidor do Blazor Web App, que contém um exemplo de ponto de extremidade de API Mínima para dados meteorológicos.
  • BlazorWebAppEntra.Client: projeto do cliente do Blazor Web App.
  • MinimalApiJwt: API Web de back-end, que contém um exemplo de ponto de extremidade de API Mínima 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 BlazorWebAppEntra pasta para .NET 9 ou posterior.

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

Exibir ou baixar 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 BlazorWebAppEntra 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. Os IDs do locatário e do cliente da API Web são usados 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 (BlazorWebAppEntra) com uma configuração de plataforma Web com duas entradas em URI de Redirecionamento: https://localhost/signin-oidc e https://localhost/signout-callback-oidc (as portas não são necessárias nesses URIs). Defina a URL de logoff do front-channel como https://localhost/signout-callback-oidc (uma porta não é necessária). O ID do locatário, o domínio do locatário e o ID do cliente do aplicativo, juntamente com o endereço base da API Web, o URI de ID do Aplicativo e o nome do escopo de clima, são usados para configurar o aplicativo no arquivo appsettings.json. 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 do Entra ou Azure, não marque 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.

Projeto Blazor Web App do servidor (BlazorWebAppEntra)

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

Projeto Blazor Web App do cliente (BlazorWebAppEntra.Client)

O projeto BlazorWebAppEntra.Client é o projeto do lado cliente do Blazor Web App.

Se o usuário precisar fazer logon ou logout durante a renderização do lado do cliente, um recarregamento de página inteira será iniciado.

Projeto de API Web de back-end (MinimalApiJwt)

O projeto MinimalApiJwt é uma API Web de back-end para vários projetos de front-end. O projeto configura um endpoint de API mínima 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.

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 atributo [Authorize] ao controlador ou à ação.

Configurar o projeto da API Web de back-end (MinimalApiJwt)

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

Para o registro do aplicativo de API Web, o Weather.Get escopo é configurado no portal do Entra ou do Azure em Expor uma API.

Authority: define a autoridade para fazer as chamadas OIDC.

jwtOptions.Authority = "{AUTHORITY}";

Os exemplos a seguir usam uma ID de locatário aaaabbbb-0000-cccc-1111-dddd2222eeee e um nome de diretório de contoso.

Se o aplicativo estiver registrado em um locatário ME-ID, a autoridade deverá corresponder ao emissor (iss) do JWT retornado pelo provedor de identidade:

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

Se o aplicativo estiver registrado em um locatário da ID Externa do Microsoft Entra:

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

Se o aplicativo estiver registrado em um locatário do AAD B2C:

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

Note

O Azure Active Directory B2C não está mais disponível como um serviço para novos clientes a partir de 1º de maio de 2025. Os locatários do AAD B2C recebem suporte para clientes com contas criadas antes de 1º de maio de 2025 até 2030. Para obter mais informações, consulte Azure AD B2C: Perguntas frequentes (perguntas frequentes).

Audience define o Público-Alvo de qualquer token de acesso JWT recebido.

jwtOptions.Audience = "{AUDIENCE}";

Corresponda o valor somente ao caminho do URI da ID do Aplicativo configurado ao adicionar o escopo Weather.Get em Expor uma API no portal Entra ou Azure. Não inclua o nome do escopo, "Weather.Get", no valor.

Os exemplos a seguir usam uma ID de aplicativo (cliente) de 11112222-bbbb-3333-cccc-4444dddd5555. O terceiro exemplo usa um domínio de locatário de contoso.onmicrosoft.com.

Exemplo de locatário ME-ID:

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

Locatário do Microsoft Entra External ID:

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

Exemplo de locatário AAD B2C:

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

Configurar o projeto do servidor (BlazorWebAppEntra)

AddMicrosoftIdentityWebApp do pacote Web pacote NuGetMicrosoft Identity (Microsoft.Identity.Web, documentação da API) está configurada no arquivo BlazorWebAppEntra do projeto Program.

Obtenha a ID do aplicativo (cliente), o domínio do locatário (editor) e a ID do diretório (locatário) do registro do aplicativo no portal do Entra ou do Azure. O URI do ID do Aplicativo é obtido para o escopo Weather.Get do registro da API Web. Não inclua o nome do escopo ao obter o URI de ID do Aplicativo do portal.

A configuração de autenticação depende do tipo de locatário:

ME-ID configuração de inquilino

Esta seção se aplica a um aplicativo registrado em um Microsoft Entra ID ou inquilino do Azure AAD B2C.

No arquivo BlazorWebAppEntra do projeto Program, forneça os valores para os seguintes espaços reservados na configuração Web Identity da Microsoft:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = "{CLIENT ID (BLAZOR APP)}";
        msIdentityOptions.Domain = "{DIRECTORY NAME}.onmicrosoft.com";
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = "{TENANT ID}";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "{BASE ADDRESS}";
        configOptions.Scopes = ["{APP ID URI}/Weather.Get"];
    })
    .AddDistributedTokenCaches();

Espaços reservados na configuração anterior:

  • {CLIENT ID (BLAZOR APP)}: A ID do aplicativo (cliente).
  • {DIRECTORY NAME}: o nome do diretório do domínio locatário (publicador).
  • {TENANT ID}: a ID do diretório (locatário).
  • {BASE ADDRESS}: o endereço base da API Web.
  • {APP ID URI}: o URI da ID do aplicativo para escopos da API Web. Qualquer um dos seguintes formatos é usado, onde o espaço reservado {CLIENT ID (WEB API)} é o ID do cliente do registro Entra da API Web e o espaço reservado {DIRECTORY NAME} é o nome do diretório do domínio do locatário (publicadores) (exemplo: contoso).
    • Formato de locatário ME-ID: api://{CLIENT ID (WEB API)}
    • Formato de locatário B2C: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

Example:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
        msIdentityOptions.Domain = "contoso.onmicrosoft.com";
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = "aaaabbbb-0000-cccc-1111-dddd2222eeee";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "https://localhost:7277";
        configOptions.Scopes = ["api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"];
    })
    .AddDistributedTokenCaches();

Configuração de ID Externa no Microsoft Entra

Esta seção se aplica a um aplicativo registrado em um locatário da ID Externa do Microsoft Entra.

No arquivo BlazorWebAppEntra do projeto Program, forneça os valores para os seguintes espaços reservados na configuração Web Identity da Microsoft:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.Authority = "https://{DIRECTORY NAME}.ciamlogin.com/{TENANT ID}/v2.0";
        msIdentityOptions.ClientId = "{CLIENT ID (BLAZOR APP)}";
        msIdentityOptions.ResponseType = "code";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "{BASE ADDRESS}";
        configOptions.Scopes = ["{APP ID URI}/Weather.Get"];
    })
    .AddDistributedTokenCaches();

Espaços reservados na configuração anterior:

  • {DIRECTORY NAME}: o nome do diretório do domínio locatário (publicador).
  • {CLIENT ID (BLAZOR APP)}: A ID do aplicativo (cliente).
  • {BASE ADDRESS}: o endereço base da API Web.
  • {APP ID URI}: o URI da ID do aplicativo para escopos da API Web. Qualquer um dos seguintes formatos é utilizado, onde o espaço reservado {CLIENT ID (WEB API)} é o ID do cliente do registro Entra da API web, e o espaço reservado {DIRECTORY NAME} é o nome do diretório do domínio do locatário (editorial) (exemplo: contoso).
    • ME-ID ou formato de tenant da ID Externa do Microsoft Entra: api://{CLIENT ID (WEB API)}
    • Formato de locatário B2C: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

Example:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.Authority = "https://contoso.ciamlogin.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0";
        msIdentityOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
        msIdentityOptions.ResponseType = "code";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "https://localhost:7277";
        configOptions.Scopes = ["api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"];
    })
    .AddDistributedTokenCaches();

O caminho de retorno de chamada (CallbackPath) deve corresponder ao URI de redirecionamento (caminho de retorno de chamada de logon) configurado no registro do aplicativo no portal do Entra ou no Azure. Os caminhos são configurados na folha Autenticação do registro do aplicativo. O valor padrão de CallbackPath é /signin-oidc para um URI de redirecionamento registrado de https://localhost/signin-oidc (uma porta não é necessária).

A SignedOutCallbackPath é o caminho de solicitação dentro do caminho base do aplicativo interceptado pelo manipulador do OpenID Connect onde o agente do usuário é retornado pela primeira vez após sair do Entra. O aplicativo de exemplo não define um valor para o caminho porque o valor padrão de "/signout-callback-oidc" é usado. Depois de interceptar a solicitação, o manipulador do OpenID Connect redireciona para o SignedOutRedirectUri ou RedirectUri, se especificado.

Warning

Não armazene segredos de 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 dados e credenciais confidenciais com segurança.

Esta versão do artigo aborda a implementação do Entra com o Padrão BFF (Back-end para Front-end). Altere o seletor de versão do artigo para padrão não BFF se a especificação do aplicativo não chamar para a adoção do padrão BFF.

A seguinte especificação é abordada:

  • O Blazor Web App usa o modo de renderização automática com interatividade global (InteractiveAuto).
  • O projeto do servidor chama AddAuthenticationStateSerialization para adicionar um provedor de estado de autenticação do lado do servidor que usa PersistentComponentState para transmitir 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 é fixo durante a duração do aplicativo WebAssembly.
  • O aplicativo usa Microsoft Entra ID, com base nos pacotes Microsoft Identity Web.
  • A atualização automática de token não interativa é gerenciada pela estrutura.
  • O padrão BFF (Back-end for Front-end) é adotado usando o Aspire para descoberta de serviço e o YARP para solicitações de proxy a um ponto de extremidade de previsão do tempo no aplicativo de back-end.
    • Uma API Web de back-end usa a autenticação de portador JWT para validar tokens JWT salvos pelo Blazor Web App no cookie de entrada.
    • 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.
  • O aplicativo usa abstrações de serviço do lado do servidor e do cliente para exibir dados meteorológicos gerados.
    • Ao renderizar o Weather componente no servidor para exibir dados meteorológicos, o componente usa o ServerWeatherForecaster. Os pacotes Web da Microsoft Identity fornecem a API para criar um serviço Web downstream nomeado para fazer chamadas à API Web. IDownstreamApi é injetado no ServerWeatherForecaster, que é usado para chamar CallApiForUserAsync para obter dados meteorológicos de uma API web externa (de um projeto MinimalApiJwt).
    • Quando o componente Weather é renderizado no cliente, o componente usa a implementação do serviço ClientWeatherForecaster, que utiliza uma HttpClient pré-configurada (no arquivo Program do projeto cliente) para fazer uma chamada de API web à API mínima do projeto servidor (/weather-forecast) para obter dados sobre o clima. O ponto de extremidade de API mínimo obtém um token de acesso para o usuário ao chamar GetAccessTokenForUserAsync. Juntamente com os escopos corretos, uma chamada de proxy reverso é feita à API Web externa (projeto MinimalApiJwt) para obter e retornar dados meteorológicos ao cliente, para que esses dados sejam renderizados pelo componente.

Prerequisites

O 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

A solução 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 Web de back-end, que contém um exemplo de ponto de extremidade de API Mínima para dados meteorológicos.
  • BlazorWebAppEntra: projeto do servidor do Blazor Web App.
  • BlazorWebAppEntra.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 BlazorWebAppEntraBff pasta para .NET 9 ou posterior.

Exibir ou baixar 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 BlazorWebAppEntra 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. Os IDs do locatário e do cliente da API Web são usados 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 (BlazorWebAppEntra) com uma configuração de plataforma Web com duas entradas em URI de Redirecionamento: https://localhost/signin-oidc e https://localhost/signout-callback-oidc (as portas não são necessárias nesses URIs). O ID do locatário, o domínio do locatário e o ID do cliente do aplicativo, juntamente com o endereço base da API Web, o URI de ID do Aplicativo e o nome do escopo de clima, são usados para configurar o aplicativo no arquivo appsettings.json. 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 do Entra ou Azure, não marque 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.

Aspire projetos

Para mais informações sobre como usar Aspire e detalhes sobre os projetos do aplicativo de exemplo .AppHost e .ServiceDefaults, consulte a documentação do 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 mais informações, incluindo um exemplo de perfis de configurações de inicialização inseguros e seguros, consulte Permitir o transporte não seguro no Aspire (documentação do Aspire).

Projeto Blazor Web App do servidor (BlazorWebAppEntra)

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

Projeto Blazor Web App do cliente (BlazorWebAppEntra.Client)

O projeto BlazorWebAppEntra.Client é o projeto do lado cliente do Blazor Web App.

Se o usuário precisar fazer logon ou logout durante a renderização do lado do cliente, um recarregamento de página inteira será iniciado.

Projeto de API Web de back-end (MinimalApiJwt)

O projeto MinimalApiJwt é uma API Web de back-end para vários projetos de front-end. O projeto configura um endpoint de API mínima para dados meteorológicos. As solicitações do projeto do lado do servidor Blazor Web App (BlazorWebAppEntra) 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.

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 atributo [Authorize] ao controlador ou à ação.

Configurar o projeto da API Web de back-end (MinimalApiJwt)

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

Para o registro do aplicativo de API Web, o Weather.Get escopo é configurado no portal do Entra ou do Azure em Expor uma API.

Authority: define a autoridade para fazer as chamadas OIDC.

jwtOptions.Authority = "{AUTHORITY}";

Os exemplos a seguir usam um ID do Locatário de aaaabbbb-0000-cccc-1111-dddd2222eeee e um nome de diretório de contoso.

Se o aplicativo estiver registrado em um locatário ME-ID, a autoridade deverá corresponder ao emissor (iss) do JWT retornado pelo provedor de identidade:

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

Se o aplicativo estiver registrado em um locatário do Microsoft Entra External ID:

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

Se o aplicativo estiver registrado em um locatário do AAD B2C:

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

Note

O Azure Active Directory B2C não está mais disponível como um serviço para novos clientes a partir de 1º de maio de 2025. Os locatários do AAD B2C receberão suporte até 2030 para os clientes com contas estabelecidas antes de 1º de maio de 2025. Para obter mais informações, consulte Azure AD B2C: Perguntas frequentes (perguntas frequentes).

Audience define o Público-Alvo de qualquer token de acesso JWT recebido.

jwtOptions.Audience = "{AUDIENCE}";

Corresponda o valor somente ao caminho do URI da ID do Aplicativo configurado ao adicionar o escopo Weather.Get em Expor uma API no portal Entra ou Azure. Não inclua o nome do escopo, "Weather.Get", no valor.

Os exemplos a seguir usam uma ID de aplicativo (cliente) de 11112222-bbbb-3333-cccc-4444dddd5555. O terceiro exemplo usa um domínio de locatário de contoso.onmicrosoft.com.

Exemplo de locatário ME-ID:

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

Locatário do ID Externo do Microsoft Entra:

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

Exemplo de locatário AAD B2C:

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

Configurar o projeto do servidor (BlazorWebAppEntra)

AddMicrosoftIdentityWebApp do pacote Web pacote NuGetMicrosoft Identity (Microsoft.Identity.Web, documentação da API) está configurada no arquivo BlazorWebAppEntra do projeto Program.

Obtenha a ID do aplicativo (cliente), o domínio do locatário (editor) e a ID do diretório (locatário) do registro do aplicativo no portal do Entra ou do Azure. O URI do ID do Aplicativo é obtido para o escopo Weather.Get do registro da API Web. Não inclua o nome do escopo ao obter o URI de ID do Aplicativo do portal.

A configuração de autenticação depende do tipo de locatário:

ME-ID configuração de inquilino

Esta seção se aplica a um aplicativo registrado em um locatário do Microsoft Entra ID ou Azure AD B2C.

No arquivo BlazorWebAppEntra do projeto Program, forneça os valores para os seguintes espaços reservados na configuração Web Identity da Microsoft:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = "{CLIENT ID (BLAZOR APP)}";
        msIdentityOptions.Domain = "{DIRECTORY NAME}.onmicrosoft.com";
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = "{TENANT ID}";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "{BASE ADDRESS}";
        configOptions.Scopes = ["{APP ID URI}/Weather.Get"];
    })
    .AddDistributedTokenCaches();

Forneça o mesmo escopo de API downstream para o transformador de solicitação:

List<string> scopes = ["{APP ID URI}/Weather.Get"];

Espaços reservados na configuração anterior:

  • {CLIENT ID (BLAZOR APP)}: A ID do aplicativo (cliente).
  • {DIRECTORY NAME}: o nome do diretório do domínio locatário (publicador).
  • {TENANT ID}: a ID do diretório (locatário).
  • {BASE ADDRESS}: o endereço base da API Web.
  • {APP ID URI}: o URI da ID do aplicativo para escopos da API Web. Qualquer um dos seguintes formatos é usado, onde o espaço reservado {CLIENT ID (WEB API)} é o ID do cliente do registro Entra da API Web e o espaço reservado {DIRECTORY NAME} é o nome do diretório do domínio do locatário (publicadores) (exemplo: contoso).
    • Formato de locatário ME-ID: api://{CLIENT ID (WEB API)}
    • Formato de locatário B2C: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

Example:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
        msIdentityOptions.Domain = "contoso.onmicrosoft.com";
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = "aaaabbbb-0000-cccc-1111-dddd2222eeee";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "https://localhost:7277";
        configOptions.Scopes = 
            ["api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"];
    })
    .AddDistributedTokenCaches();

Example:

List<string> scopes = ["api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"];

Configuração da Identidade Externa do Microsoft Entra

Esta seção se aplica a um aplicativo registrado em um locatário da ID Externa do Microsoft Entra.

No arquivo BlazorWebAppEntra do projeto Program, forneça os valores para os seguintes espaços reservados na configuração Web Identity da Microsoft:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.Authority = "https://{DIRECTORY NAME}.ciamlogin.com/{TENANT ID}/v2.0";
        msIdentityOptions.ClientId = "{CLIENT ID (BLAZOR APP)}";
        msIdentityOptions.ResponseType = "code";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "{BASE ADDRESS}";
        configOptions.Scopes = ["{APP ID URI}/Weather.Get"];
    })
    .AddDistributedTokenCaches();

Forneça o mesmo escopo de API downstream para o transformador de solicitação:

List<string> scopes = ["{APP ID URI}/Weather.Get"];

Espaços reservados na configuração anterior:

  • {DIRECTORY NAME}: o nome do diretório do domínio locatário (publicador).
  • {CLIENT ID (BLAZOR APP)}: A ID do aplicativo (cliente).
  • {BASE ADDRESS}: o endereço base da API Web.
  • {APP ID URI}: o URI da ID do aplicativo para escopos da API Web. Qualquer um dos seguintes formatos é usado, onde o espaço reservado {CLIENT ID (WEB API)} é o ID do cliente do registro Entra da API Web e o espaço reservado {DIRECTORY NAME} é o nome do diretório do domínio do locatário (publicadores) (exemplo: contoso).
    • ME-ID ou formato de tenant da ID Externa do Microsoft Entra: api://{CLIENT ID (WEB API)}
    • Formato de locatário B2C: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

Example:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.Authority = "https://contoso.ciamlogin.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0";
        msIdentityOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
        msIdentityOptions.ResponseType = "code";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "https://localhost:7277";
        configOptions.Scopes = ["api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"];
    })
    .AddDistributedTokenCaches();

Example:

List<string> scopes = ["api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"];

Warning

Os aplicativos de produção devem usar um provedor de cache distribuído de tokens para produção. Caso contrário, o aplicativo poderá ter um desempenho ruim em alguns cenários. Para obter mais informações, consulte a seção Usar um provedor de cache de produção de token distribuído.

O caminho de retorno de chamada (CallbackPath) deve corresponder ao URI de redirecionamento (caminho de retorno de chamada de logon) configurado no registro do aplicativo no portal do Entra ou no Azure. Os caminhos são configurados na folha Autenticação do registro do aplicativo. O valor padrão de CallbackPath é /signin-oidc para um URI de redirecionamento registrado de https://localhost/signin-oidc (uma porta não é necessária).

A SignedOutCallbackPath é o caminho de solicitação dentro do caminho base do aplicativo interceptado pelo manipulador do OpenID Connect onde o agente do usuário é retornado pela primeira vez após sair do Entra. O aplicativo de exemplo não define um valor para o caminho porque o valor padrão de "/signout-callback-oidc" é usado. Depois de interceptar a solicitação, o manipulador do OpenID Connect redireciona para o SignedOutRedirectUri ou RedirectUri, se especificado.

Warning

Não armazene segredos de 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 dados e credenciais confidenciais com segurança.

Definir o segredo do cliente

Esta seção só se aplica ao projeto de servidor do Blazor Web App.

Use uma ou ambas as abordagens a seguir para fornecer o segredo do cliente ao aplicativo:

  • Ferramenta Gerenciador de Segredos: A ferramenta Gerenciador de Segredos armazena dados privados na máquina local e é usada apenas durante o desenvolvimento local.
  • Azure Key Vault: você pode armazenar o segredo do cliente em um cofre de chaves para uso em qualquer ambiente, inclusive para o ambiente de desenvolvimento ao trabalhar localmente. Alguns desenvolvedores preferem usar cofres de chaves para implantações de produção e preparação e usam a ferramenta Gerenciador de Segredos para desenvolvimento local.

É altamente recomendável que você evite armazenar segredos do cliente no código do projeto ou nos arquivos de configuração. Use fluxos de autenticação seguros, como uma ou ambas as abordagens nesta seção.

Ferramenta Gerenciador de segredos

A ferramenta Gerenciador de segredos pode armazenar o segredo do cliente do aplicativo servidor na chave AzureAd:ClientSecretde configuração.

O Blazor aplicativo de 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 servidor, que é usado internamente pelas ferramentas para rastrear segredos 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 Entra do aplicativo:

dotnet user-secrets set "AzureAd: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.

Azure Key Vault

O Azure Key Vault fornece uma abordagem segura para fornecer o segredo do cliente do aplicativo para o aplicativo.

Para criar um cofre de chaves e definir um segredo do cliente, consulte Sobre os segredos do Azure Key Vault (documentação do Azure), que vincula recursos para começar a usar o Azure Key Vault. Para implementar o código nesta seção, registre o URI do cofre de chaves e o nome secreto do Azure ao criar o cofre de chaves e o segredo. Para o exemplo nesta seção, o nome do segredo é "BlazorWebAppEntraClientSecret.

Ao configurar o cofre de chaves no portal do Microsoft Entra ou do Azure:

  • Configure o cofre de chaves para usar o RABC (controle de acesso baseado em função) do Azure. Se você não estiver operando em uma Rede Virtual do Azure, inclusive para desenvolvimento local e teste, confirme se o acesso público na etapa rede está habilitado (verificado). A habilitação do acesso público só expõe o ponto de extremidade do cofre de chaves. Contas autenticadas ainda são necessárias para acesso.

  • Crie um Recurso Gerenciado Identity do Azure (ou adicione uma função ao Recurso Gerenciado Identity existente que você planeja usar) com a função Usuário de Segredos do Key Vault. Atribua o Identity Gerenciado ao Serviço de Aplicativo do Azure que está hospedando a implantação: Configurações>Identity>Atribuído pelo usuário>Adicionar.

    Note

    Se você também planeja executar um aplicativo localmente com um usuário autorizado para acesso ao cofre de chaves usando a CLI do Azure ou a Autenticação de Serviço do Azure do Visual Studio, adicione sua conta de usuário do Azure para desenvolvedor no Controle de Acesso (IAM) com a função de Usuário de Segredos do Key Vault . Se você quiser usar a CLI do Azure por meio do Visual Studio, execute o az login comando no painel do PowerShell do Desenvolvedor e siga os prompts para autenticar com o locatário.

Para implementar o código nesta seção, registre o URI do cofre de chaves (exemplo: "https://contoso.vault.azure.net/", barra à direita necessária) e o nome do segredo (exemplo: "BlazorWebAppEntraClientSecret") do Azure ao criar o cofre de chaves e o segredo.

Important

Um segredo de cofre de chaves é criado com uma data de expiração. Certifique-se de acompanhar quando um segredo do cofre de chaves vai expirar e criar um novo segredo para o aplicativo antes que essa data passe.

Adicione a seguinte classe AzureHelper ao projeto do servidor. O método GetKeyVaultSecret recupera um segredo de um cofre de chaves. Ajuste o namespace (BlazorSample.Helpers) para corresponder ao esquema de namespace do projeto.

Helpers/AzureHelper.cs:

using Azure.Core;
using Azure.Security.KeyVault.Secrets;

namespace BlazorWebAppEntra.Helpers;

public static class AzureHelper
{
    public static string GetKeyVaultSecret(string vaultUri, 
        TokenCredential credential, string secretName)
    {
        var client = new SecretClient(new Uri(vaultUri), credential);
        var secret = client.GetSecretAsync(secretName).Result;

        return secret.Value.Value;
    }
}

Note

O exemplo anterior usa DefaultAzureCredential para simplificar a autenticação ao desenvolver aplicativos que serão implantados no Azure, combinando credenciais utilizadas em ambientes de hospedagem do Azure com aquelas utilizadas no desenvolvimento local. Ao migrar para a produção, é melhor escolher uma alternativa, como ManagedIdentityCredential. Para obter mais informações, consulte Autenticar aplicativos .NET hospedados no Azure em recursos do Azure usando uma identidade gerenciada atribuída pelo sistema.

Quando os serviços são registrados no arquivo Program do projeto do servidor, use o seguinte código para obter e aplicar o segredo do cliente:

TokenCredential? credential;

if (builder.Environment.IsProduction())
{
    credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}");
}
else
{
    // Local development and testing only
    DefaultAzureCredentialOptions options = new()
    {
        // Specify the tenant ID to use the dev credentials when running the app locally
        // in Visual Studio.
        VisualStudioTenantId = "{TENANT ID}",
        SharedTokenCacheTenantId = "{TENANT ID}"
    };

    credential = new DefaultAzureCredential(options);
}

Onde MicrosoftIdentityOptions estão definidos, chame GetKeyVaultSecret para receber e atribuir o segredo de cliente do aplicativo:

msIdentityOptions.ClientSecret = AzureHelper.GetKeyVaultSecret("{VAULT URI}", 
    credential, "{SECRET NAME}");

{MANAGED IDENTITY CLIENT ID}: o ID do cliente gerenciado do Azure Identity (GUID).

{TENANT ID}: a ID do diretório (locatário). Exemplo: aaaabbbb-0000-cccc-1111-dddd2222eeee

{VAULT URI}: URI do cofre de chaves. Inclua a barra final no URI. Exemplo: https://contoso.vault.azure.net/

{SECRET NAME}: Nome secreto. Exemplo: BlazorWebAppEntraClientSecret

A configuração é usada para facilitar o fornecimento de cofres de chaves dedicados e nomes secretos com base nos arquivos de configuração ambiental do aplicativo. Por exemplo, você pode fornecer valores de configuração diferentes para appsettings.Development.json no ambiente de desenvolvimento, appsettings.Staging.json no ambiente de preparação e appsettings.Production.json para a implantação de produção. Para obter mais informações, consulte Configuração no ASP.NET Core Blazor.

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

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 Identity autenticação de portador JWT e Web da Microsoft em seus arquivos Program para tornar as configurações detectáveis usando preenchimento automático do 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 a partir dos 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 BlazorWebAppEntra projeto, adicione a seguinte configuração JSON:

{
  "AzureAd": {
    "CallbackPath": "/signin-oidc",
    "ClientId": "{CLIENT ID (BLAZOR APP)}",
    "Domain": "{DIRECTORY NAME}.onmicrosoft.com",
    "Instance": "https://login.microsoftonline.com/",
    "ResponseType": "code",
    "TenantId": "{TENANT ID}"
  },
  "DownstreamApi": {
    "BaseUrl": "{BASE ADDRESS}",
    "Scopes": ["{APP ID URI}/Weather.Get"]
  }
}

Atualize os marcadores de posição na configuração anterior para corresponder aos valores que o aplicativo usa no arquivo Program.

  • {CLIENT ID (BLAZOR APP)}: A ID do aplicativo (cliente).
  • {DIRECTORY NAME}: o nome do diretório do domínio locatário (publicador).
  • {TENANT ID}: a ID do diretório (locatário).
  • {BASE ADDRESS}: o endereço base da API Web.
  • {APP ID URI}: o URI da ID do aplicativo para escopos da API Web. Qualquer um dos seguintes formatos é usado, onde o espaço reservado {CLIENT ID (WEB API)} é o ID do cliente do registro Entra da API Web e o espaço reservado {DIRECTORY NAME} é o nome do diretório do domínio do locatário (publicadores) (exemplo: contoso).
    • Formato de locatário ME-ID: api://{CLIENT ID (WEB API)}
    • Formato de locatário B2C: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

Example:

"AzureAd": {
  "CallbackPath": "/signin-oidc",
  "ClientId": "00001111-aaaa-2222-bbbb-3333cccc4444",
  "Domain": "contoso.onmicrosoft.com",
  "Instance": "https://login.microsoftonline.com/",
  "ResponseType": "code",
  "TenantId": "aaaabbbb-0000-cccc-1111-dddd2222eeee"
},
"DownstreamApi": {
  "BaseUrl": "https://localhost:7277",
  "Scopes": ["api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"]
}

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 detectada pelo módulo de autenticação.

Faça as seguintes alterações no arquivo Program:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
-   .AddMicrosoftIdentityWebApp(msIdentityOptions =>
-   {
-       msIdentityOptions.CallbackPath = "...";
-       msIdentityOptions.ClientId = "...";
-       msIdentityOptions.Domain = "...";
-       msIdentityOptions.Instance = "...";
-       msIdentityOptions.ResponseType = "...";
-       msIdentityOptions.TenantId = "...";
-   })
+   .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
-   .AddDownstreamApi("DownstreamApi", configOptions =>
-   {
-       configOptions.BaseUrl = "...";
-       configOptions.Scopes = ["..."];
-   })
+   .AddDownstreamApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi"))
    .AddDistributedTokenCaches();
- List<string> scopes = ["{APP ID URI}/Weather.Get"];
- var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
+ var configuration = transformContext.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
+ var scopes = configuration.GetSection("DownstreamApi:Scopes").Get<IEnumerable<string>>();
+ var accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes ??
+     throw new InvalidOperationException("No downstream API scopes!"));

Note

Os aplicativos de produção devem usar um provedor de cache distribuído de tokens para produção. Caso contrário, o aplicativo poderá ter um desempenho ruim em alguns cenários. Para obter mais informações, consulte a seção Usar um provedor de cache de produção de token distribuído.

No projeto MinimalApiJwt, adicione a seguinte configuração 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 marcadores de posição na configuração anterior para corresponder aos valores que o aplicativo usa no arquivo Program.

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

Os formatos de autoridade seguem os seguintes padrões:

  • Tipo de locatário ME-ID: https://sts.windows.net/{TENANT ID}
  • ID Externa 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):

  • Tipo de locatário ME-ID: 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 reconhecida pelo construtor de autenticação do 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 um provedor de cache de produção de token distribuído

Caches de token distribuídos na memória são criados ao chamar AddDistributedTokenCaches para assegurar uma implementação básica disponível para isso.

Aplicativos Web de produção e APIs Web devem usar um cache de token distribuído de produção (por exemplo: Redis, Microsoft SQL Server, Microsoft Azure Cosmos DB).

Note

Para desenvolvimento local e teste em um único computador, você pode usar caches de token na memória em vez de caches de token distribuído:

builder.Services.AddInMemoryTokenCaches();

Posteriormente, no período de desenvolvimento e teste, adote um provedor de cache distribuído de tokens para produção.

AddDistributedMemoryCache adiciona uma implementação padrão de IDistributedCache que armazena itens de cache na memória, os quais são usados pelo Microsoft Identity Web para cache de token.

O cache de token distribuído é configurado por MsalDistributedTokenCacheAdapterOptions:

builder.Services.AddDistributedMemoryCache();

builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(
    options => 
    {
      // The following lines that are commented out reflect
      // default values. We recommend overriding the default
      // value of Encrypt to encrypt tokens at rest.

      //options.DisableL1Cache = false;
      //options.L1CacheOptions.SizeLimit = 500 * 1024 * 1024;
      options.Encrypt = true;
      //options.SlidingExpiration = TimeSpan.FromHours(1);
    });

AddDistributedMemoryCache requer uma referência de pacote ao Microsoft.Extensions.Caching.Memory pacote NuGet.

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.

Para configurar um provedor de cache distribuído em ambiente de produção, consulte Cache distribuído no ASP.NET Core.

Warning

Sempre substitua os caches de tokens distribuídos em memória por um provedor de cache de token real quando for implantar o aplicativo em ambiente de produção. Se você não adotar um provedor de cache de token distribuído de produção, o aplicativo poderá sofrer um desempenho significativamente degradado.

Para obter mais informações, consulte Serialização de cache de token: caches distribuídos. No entanto, os exemplos de código mostrados não se aplicam a aplicativos ASP.NET Core, que configuram caches distribuídos por meio de AddDistributedMemoryCache, não AddDistributedTokenCache.

Use um chaveiro de Proteção de Dados compartilhado em produção para que as instâncias do aplicativo entre servidores em um web farm possam descriptografar tokens quando MsalDistributedTokenCacheAdapterOptions.Encrypt estiver definido como true.

Note

Para desenvolvimento antecipado e teste local em um único computador, você pode definir Encryptfalse e configurar um anel de chave de Proteção de Dados compartilhado posteriormente:

options.Encrypt = false;

Posteriormente, no período de desenvolvimento e teste, habilite a criptografia de token e adote um anel de chave de Proteção de Dados compartilhado.

O exemplo a seguir mostra como usar o Armazenamento de Blobs do Azure e o Azure Key Vault (PersistKeysToAzureBlobStorage/ProtectKeysWithAzureKeyVault) para o anel de chave compartilhado. As configurações de serviço são cenários de caso base para fins de demonstração. Antes de implantar aplicativos de produção, familiarize-se com os serviços do Azure e adote as práticas recomendadas usando os conjuntos de documentação dedicados dos serviços do Azure, que estão vinculados no final desta seção.

Confirme a presença dos seguintes pacotes no projeto de servidor do Blazor Web App:

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.

Note

Antes de prosseguir com as etapas a seguir, confirme se o aplicativo está registrado no Microsoft Entra.

O código a seguir normalmente é implementado ao mesmo tempo em que um provedor de cache de token distribuído de produção é implementado. Outras opções, no Azure e fora do Azure, estão disponíveis para gerenciar chaves de proteção de dados em várias instâncias de aplicativo, mas o aplicativo de exemplo demonstra como usar os serviços do Azure.

Configure o Armazenamento de Blobs do Azure para manter as chaves de proteção de dados. Siga as diretrizes nos principais provedores de armazenamento no ASP.NET Core.

Configure o Azure Key Vault para criptografar as chaves de proteção de dados em repouso. Siga as diretrizes em Configurar ASP.NET Core Data Protection.

Use o seguinte código no arquivo Program em que os serviços são registrados:

TokenCredential? credential;

if (builder.Environment.IsProduction())
{
    credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}");
}
else
{
    // Local development and testing only
    DefaultAzureCredentialOptions options = new()
    {
        // Specify the tenant ID to use the dev credentials when running the app locally
        // in Visual Studio.
        VisualStudioTenantId = "{TENANT ID}",
        SharedTokenCacheTenantId = "{TENANT ID}"
    };

    credential = new DefaultAzureCredential(options);
}

builder.Services.AddDataProtection()
    .SetApplicationName("BlazorWebAppEntra")
    .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential)
    .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential);

Você pode passar qualquer nome de aplicativo para SetApplicationName. Apenas confirme se todas as implantações de aplicativo usam o mesmo valor.

{MANAGED IDENTITY CLIENT ID}: o ID do cliente gerenciado do Azure Identity (GUID).

{TENANT ID}: ID do locatário.

{BLOB URI}: URI completo para o arquivo de chave. O URI é gerado pelo Armazenamento do Azure quando você cria o arquivo de chave. Não use um SAS.

{KEY IDENTIFIER}: identificador de chave do Azure Key Vault usado para criptografia de chave. Uma política de acesso permite que o aplicativo acesse o cofre de chaves com Get, Unwrap Keye Wrap Key permissões. A versão da chave é obtida da chave no portal do Entra ou do Azure depois que ela é criada. Caso você habilite a autorrotação da chave do cofre de chaves, certifique-se de usar um identificador de chave sem versão na configuração do cofre de chaves do aplicativo, onde nenhum GUID de chave é colocado no final do identificador (exemplo: https://contoso.vault.azure.net/keys/data-protection).

Note

Em ambientes que não são de produção, o exemplo anterior usa DefaultAzureCredential para simplificar a autenticação ao desenvolver aplicativos que são implantados no Azure combinando credenciais usadas em ambientes de hospedagem do Azure com credenciais usadas no desenvolvimento local. Para obter mais informações, consulte Autenticar aplicativos .NET hospedados no Azure em recursos do Azure usando uma identidade gerenciada atribuída pelo sistema.

Como alternativa, você pode configurar o aplicativo para fornecer os valores de arquivos de configurações de aplicativo usando o Provedor de Configuração JSON. Adicione o seguinte ao arquivo de configurações do aplicativo:

"DistributedTokenCache": {
  "DisableL1Cache": false,
  "L1CacheSizeLimit": 524288000,
  "Encrypt": true,
  "SlidingExpirationInHours": 1
},
"DataProtection": {
  "BlobUri": "{BLOB URI}",
  "KeyIdentifier": "{KEY IDENTIFIER}"
}

Seção de exemplo DataProtection :

"DataProtection": {
  "BlobUri": "https://contoso.blob.core.windows.net/data-protection/keys.xml",
  "KeyIdentifier": "https://contoso.vault.azure.net/keys/data-protection"
}

Note

O identificador de chave no exemplo anterior é sem versão. Não há nenhuma versão de chave GUID no final do identificador. Isso é importante principalmente se você escolher configurar o rodízio automático de chaves. Para obter mais informações, consulte Configurar a rotação automática de chave criptográfica no Azure Key Vault: política de rotação de chaves.

Faça as seguintes alterações no arquivo Program:

builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(
    options =>
    {
+       var config = builder.Configuration.GetSection("DistributedTokenCache");

-       options.DisableL1Cache = false;
+       options.DisableL1Cache = config.GetValue<bool>("DisableL1Cache");

-       options.L1CacheOptions.SizeLimit = 500 * 1024 * 1024;
+       options.L1CacheOptions.SizeLimit = config.GetValue<long>("L1CacheSizeLimit");

-       options.Encrypt = true;
+       options.Encrypt = config.GetValue<bool>("Encrypt");

-       options.SlidingExpiration = TimeSpan.FromHours(1);
+       options.SlidingExpiration = 
+           TimeSpan.FromHours(config.GetValue<int>("SlidingExpirationInHours"));
    });

- builder.Services.AddDataProtection()
-     .SetApplicationName("BlazorWebAppEntra")
-     .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential)
-     .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential);

Adicione o seguinte código em que os serviços estão configurados no Program arquivo:

var config = builder.Configuration.GetSection("DataProtection");

builder.Services.AddDataProtection()
    .SetApplicationName("BlazorWebAppEntra")
    .PersistKeysToAzureBlobStorage(
        new Uri(config.GetValue<string>("BlobUri") ??
        throw new Exception("Missing Blob URI")),
        credential)
    .ProtectKeysWithAzureKeyVault(
        new Uri(config.GetValue<string>("KeyIdentifier") ?? 
        throw new Exception("Missing Key Identifier")), 
        credential);

Para obter mais informações sobre como usar um anel de chave de Proteção de Dados compartilhado e provedores de armazenamento de chaves, consulte os seguintes recursos:

Prefixo de destino do encaminhador YARP

O Blazor Web App encaminhador YARP do projeto de servidor, no qual o token de acesso do usuário está anexado à chamada para a API web MinimalApiJwt, especifica um prefixo de destino de https://weatherapi. Esse valor corresponde ao nome do projeto, AddProject, passado no arquivo Program do projeto Aspire.AppHost.

Encaminhador no projeto do servidor Blazor Web App (BlazorWebAppEntra):

app.MapForwarder("/weather-forecast", "https://weatherapi", transformBuilder =>
{
    ...
}).RequireAuthorization();

Nome do projeto correspondente no Program arquivo do projeto host do Aspire aplicativo (Aspire.AppHost):

var weatherApi = builder.AddProject<Projects.MinimalApiJwt>("weatherapi");

Não é necessário alterar o prefixo de destino do encaminhador YARP ao implantar o Blazor Web App para produção. O pacote de API Microsoft Identity Web Downstream utiliza o URI base passado por meio da configuração para fazer a chamada à API web do ServerWeatherForecaster, e não o prefixo de destino do encaminhador YARP. Na produção, o encaminhador YARP simplesmente transforma a solicitação, adicionando o token de acesso do usuário.

Redirecionar para a página inicial ao fazer logout

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 componente LogInOrOut a seguir, 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>

Segurança de dados meteorológicos

Para obter mais informações sobre como este aplicativo protege seus dados meteorológicos, consulte Proteção de dados em Blazor Web Apps com renderização automática interativa.

Troubleshoot

Logging

O aplicativo de servidor é um aplicativo ASP.NET Core padrão. Consulte as diretrizes de registro em log do ASP.NET Core para habilitar um nível de log mais baixo no aplicativo 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 de Registro de log do ASP.NET Core Blazor com 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 o logoff com a ID Externa do Microsoft Entra:

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

    O depurador do Visual Studio interrompe com uma 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 da 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 de comentários desta página), mas não consegue 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 são de propriedade ou controlados pela Microsoft.

    Para relatórios de bugs de estrutura reproduzível não confidenciais e não confidenciais, abra um problema com a unidade do 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 confidencial ou de natureza confidencial ou descrever uma potencial falha de segurança no produto que possa ser explorada por invasores cibernéticos, confira Relatar problemas e bugs de segurança (Repositório GitHub dotnet/aspnetcore).

  • 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 atributo allowPublicClient 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 testar se você consegue configurar a exclusão de todos os dados de cookie e sites sempre que o navegador é 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 Navegar com pelo botão Executar do 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}, em que o espaço reservado {URL} é 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