Partilhar via


Proteja um ASP.NET Core Blazor Web App com o Microsoft Entra ID

Este artigo descreve como proteger um Blazor Web App com a plataforma de identidade da Microsoft com pacotes da Web da Microsoft Identity para o Microsoft Entra ID usando uma aplicação de exemplo.

Esta versão do artigo aborda a implementação do Entra sem adotar o padrão Backend for Frontend (BFF). O padrão BFF é útil para fazer solicitações autenticadas para serviços externos. Altere o seletor de versão do artigo para padrão BFF se a especificação do aplicativo exigir a adoção do padrão BFF.

A seguinte especificação é abrangida:

  • O Blazor Web App usa o modo de renderização automática com interatividade global (InteractiveAuto).
  • O projeto de servidor chama AddAuthenticationStateSerialization para adicionar um fornecedor de estado de autenticação do lado do servidor que utiliza 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 para o tempo de vida do aplicativo WebAssembly.
  • A aplicação usa Microsoft Entra ID, com base em pacotes de Microsoft Identity Web.
  • A atualização automática de tokens não interativos é gerida pelo framework.
  • 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 uma API para criar um serviço web nomeado de downstream, a fim de realizar chamadas a APIs web. IDownstreamApi é injetado no ServerWeatherForecaster, e é utilizado para invocar CallApiForUserAsync a fim de obter dados meteorológicos de uma API da Web externa (projeto MinimalApiJwt).
    • Quando o componente Weather é renderizado no cliente, o componente usa a implementação de serviço ClientWeatherForecaster, que utiliza um HttpClient pré-configurado (no ficheiro Program do projeto cliente) para fazer uma chamada de API Web ao Minimal API (/weather-forecast) do projeto de servidor para obter dados meteorológicos. O ponto de extremidade da API Minimal obtém os dados meteorológicos da classe ServerWeatherForecaster e os retorna ao cliente para processamento pelo componente.

Solução de amostra

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

  • BlazorWebAppEntra: Projeto lado servidor do Blazor Web App, contendo um exemplo de API mínima endpoint para dados meteorológicos.
  • BlazorWebAppEntra.Client: Projeto cliente do Blazor Web App.
  • MinimalApiJwt: API de back-end da web, contendo um exemplo de Minimal API endpoint para dados meteorológicos.

Acesse o exemplo através da pasta da versão mais recente no repositório de exemplos Blazor com o link a seguir. O exemplo está na pasta BlazorWebAppEntra para .NET 9 ou posterior.

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

Ver ou baixar código de amostra (como baixar)

Registos da aplicação Microsoft Entra ID

Recomendamos o uso de registros separados para aplicativos e APIs da Web, mesmo quando os aplicativos e as APIs da Web estiverem na mesma solução. A orientação a seguir é para o aplicativo BlazorWebAppEntra e a API da Web MinimalApiJwt da solução de exemplo, mas a mesma orientação se aplica geralmente a qualquer inscrição baseada em Entra para aplicativos e APIs da Web.

Registre a API da Web (MinimalApiJwt) primeiro para que você possa conceder acesso à API da Web ao registrar o aplicativo. O ID do tenant e o ID do cliente da Web API são utilizados para configurar a Web API no seu ficheiro Program. Depois de registar a API da Web, exponha a API da Web em Registos de Aplicações>Expor uma API com o 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 e um URI de redirecionamento de https://localhost/signin-oidc (uma porta não é necessária). O ID do locatário do aplicativo, o domínio do locatário e o ID do cliente, juntamente com o endereço base da API Web, o URI do ID do aplicativo e o nome do escopo de meteorologia, são usados para configurar o aplicativo no arquivo appsettings.json. Conceda permissão à API para aceder à API da web em Registros de aplicativos>Permissões da API. Se a especificação de segurança do aplicativo exigir, você poderá conceder consentimento de administrador para que a organização acesse a API da Web. Os utilizadores e grupos autorizados são atribuídos ao registo da aplicação em Registos de> aplicaçõesAplicações empresariais.

Na configuração de registo do portal do Entra ou do Azure para "concessão implícita e fluxos híbridos", não marque nenhuma das caixas de seleção do ponto de extremidade de autorização para que retornem tokens de acesso ou tokens de ID. O manipulador 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> do cliente). Guarde o segredo do cliente Valor para usar na próxima seção.

Orientações adicionais de configuração do Entra para configurações específicas são fornecidas mais adiante neste artigo.

Projeto Blazor Web App do lado do servidor (BlazorWebAppEntra)

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

Projeto do lado do cliente Blazor Web App (BlazorWebAppEntra.Client)

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

Se o usuário precisar fazer login ou logout durante a renderização do lado do cliente, uma recarga de página inteira será iniciada.

Projeto de API web backend (MinimalApiJwt)

O projeto MinimalApiJwt é uma API web de back-end para vários projetos de frontend. O projeto configura um endpoint de API Minimal 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 é 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 e a UI 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 Program do projeto.

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 de API Web de back-end (MinimalApiJwt)

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

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 configura a Autoridade para fazer chamadas OIDC.

jwtOptions.Authority = "{AUTHORITY}";

Os exemplos a seguir usam uma ID de locatário de aaaabbbb-0000-cccc-1111-dddd2222eeee.

Se a aplicação estiver registada num tenant ME-ID, a autoridade deve corresponder ao emissor (iss) do JWT retornado pelo fornecedor de identidade:

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

Se a aplicação estiver registada em um inquilino B2C do AAD:

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

Audience define a Audiência para qualquer token de acesso JWT recebido.

jwtOptions.Audience = "{AUDIENCE}";

Corresponda o valor exclusivamente ao caminho do URI de ID da Aplicação configurado ao adicionar o escopo Weather.Get na seção Expor uma API no portal do Entra ou do 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 segundo exemplo usa um domínio de locatário de contoso.onmicrosoft.com.

ME-ID exemplo de inquilino:

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

Exemplo de inquilino B2C do Azure AD:

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

Configurar o projeto de servidor (BlazorWebAppEntra)

AddMicrosoftIdentityWebApp da Microsoft Identity Web (Microsoft.Identity.Web pacote NuGet, documentação da API) é configurado no arquivo do BlazorWebAppEntraProgram projeto.

Obtenha o ID da aplicação (cliente), o domínio do inquilino e o ID do diretório (inquilino) a partir do registo da aplicação no portal do Entra ou do Azure. O URI do ID da aplicação é obtido para o Weather.Get escopo a partir do registo da API web. Não inclua o nome do escopo ao obter o URI da ID da aplicação no portal.

No arquivo BlazorWebAppEntra do projeto Program, forneça os valores para as seguintes variáveis de substituição na configuração Identity Web 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();

Marcadores de posição na configuração anterior:

  • {CLIENT ID (BLAZOR APP)}: O ID do aplicativo (cliente).
  • {DIRECTORY NAME}: O nome do diretório do domínio do inquilino (editor).
  • {TENANT ID}: O ID do diretório (inquilino).
  • {BASE ADDRESS}: O endereço base da API da Web.
  • {APP ID URI}: O URI do ID do aplicativo para escopos da API Web. Um dos dois seguintes formatos é usado, onde o espaço reservado {CLIENT ID (WEB API)} é o Client Id do registo Entra da API Web e o espaço reservado {DIRECTORY NAME} é o nome do diretório do domínio do tenant (editores) (exemplo: contoso).
    • ME-ID formato do locatário: api://{CLIENT ID (WEB API)}
    • Formato de locatário B2C: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

Exemplo:

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

Esta versão do artigo trata da implementação do "Entra" com o padrão Backend for Frontend (BFF) . Altere o seletor de versão do artigo para padrão não-BFF se a especificação do aplicativo não exigir a adoção do padrão BFF.

A seguinte especificação é abrangida:

  • O Blazor Web App usa o modo de renderização automática com interatividade global (InteractiveAuto).
  • O projeto de servidor chama AddAuthenticationStateSerialization para adicionar um fornecedor de estado de autenticação do lado do servidor que utiliza 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 para o tempo de vida do aplicativo WebAssembly.
  • A aplicação usa Microsoft Entra ID, com base em pacotes de Microsoft Identity Web.
  • A atualização automática de tokens não interativos é gerida pelo framework.
  • O padrão Backend for Frontend (BFF) é adotado usando o .NET Aspire para descoberta de serviços e YARP para encaminhar solicitações de proxy para um ponto de extremidade de previsão do tempo na aplicação de back-end.
    • Uma API Web de backend usa a autenticação JWT-bearer para validar tokens JWT salvos pelo Blazor Web App no cookiede início de sessão.
    • O Aspire melhora a experiência de criação de aplicações nativas da nuvem .NET. Ele fornece um conjunto consistente e opinativo de ferramentas e padrões para criar e executar aplicativos distribuídos.
    • YARP (Yet Another Reverse Proxy) é uma biblioteca usada para criar um servidor proxy reverso.
  • 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 uma API para criar um serviço web nomeado de downstream, a fim de realizar chamadas a APIs web. IDownstreamApi é injetado no ServerWeatherForecaster, e é utilizado para invocar CallApiForUserAsync a fim de obter dados meteorológicos de uma API da Web externa (projeto MinimalApiJwt).
    • Quando o componente Weather é renderizado no cliente, o componente usa a implementação de serviço ClientWeatherForecaster, que utiliza um HttpClient pré-configurado (no ficheiro Program do projeto cliente) para fazer uma chamada de API Web ao Minimal API (/weather-forecast) do projeto de servidor para obter dados meteorológicos. O endpoint Minimal API obtém um token de acesso para o utilizador chamando GetAccessTokenForUserAsync. Além dos escopos corretos, uma chamada de proxy inverso é feita na API da Web externa (MinimalApiJwt projeto) para obter e retornar dados meteorológicos ao cliente para serem processados pelo componente.

Para obter mais informações sobre .NET Aspire, consulte Disponibilidade geral do .NET Aspire: Simplificando o desenvolvimento do .NET Cloud-Native (maio, 2024).

Pré-requisitos

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

Além disso, consulte a seção Pré-requisitos de Guia de início rápido: crie seu primeiro aplicativo .NET Aspire.

Solução de amostra

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

  • .NET 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 de aplicativos .NET Aspire que podem ser estendidas e personalizadas conforme necessário.
  • MinimalApiJwt: API de back-end da web, contendo um exemplo de Minimal API endpoint para dados meteorológicos.
  • BlazorWebAppEntra: Projeto de servidor do Blazor Web App.
  • BlazorWebAppEntra.Client: Projeto cliente do Blazor Web App.

Acesse o exemplo através da pasta da versão mais recente no repositório de exemplos Blazor com o link a seguir. O exemplo está na pasta BlazorWebAppEntraBff para .NET 9 ou posterior.

Ver ou baixar código de amostra (como baixar)

Registos da aplicação Microsoft Entra ID

Recomendamos o uso de registros separados para aplicativos e APIs da Web, mesmo quando os aplicativos e as APIs da Web estiverem na mesma solução. A orientação a seguir é para o aplicativo BlazorWebAppEntra e a API da Web MinimalApiJwt da solução de exemplo, mas a mesma orientação se aplica geralmente a qualquer inscrição baseada em Entra para aplicativos e APIs da Web.

Registre a API da Web (MinimalApiJwt) primeiro para que você possa conceder acesso à API da Web ao registrar o aplicativo. O ID do tenant e o ID do cliente da Web API são utilizados para configurar a Web API no seu ficheiro Program. Depois de registar a API da Web, exponha a API da Web em Registos de Aplicações>Expor uma API com o 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 e um URI de redirecionamento de https://localhost/signin-oidc (uma porta não é necessária). O ID do locatário do aplicativo, o domínio do locatário e o ID do cliente, juntamente com o endereço base da API Web, o URI do ID do aplicativo e o nome do escopo de meteorologia, são usados para configurar o aplicativo no arquivo appsettings.json. Conceda permissão à API para aceder à API da web em Registros de aplicativos>Permissões da API. Se a especificação de segurança do aplicativo exigir, você poderá conceder consentimento de administrador para que a organização acesse a API da Web. Os utilizadores e grupos autorizados são atribuídos ao registo da aplicação em Registos de> aplicaçõesAplicações empresariais.

Na configuração de registo do portal do Entra ou do Azure para "concessão implícita e fluxos híbridos", não marque nenhuma das caixas de seleção do ponto de extremidade de autorização para que retornem tokens de acesso ou tokens de ID. O manipulador 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> do cliente). Guarde o segredo do cliente Valor para usar na próxima seção.

Orientações adicionais de configuração do Entra para configurações específicas são fornecidas mais adiante neste artigo.

.NET Aspire projetos

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

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

O aplicativo de exemplo 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 de configurações de inicialização seguras e inseguras, consulte Permitir transporte não-seguro em .NET Aspire (documentação.NET Aspire).

Projeto Blazor Web App do lado do servidor (BlazorWebAppEntra)

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

Projeto do lado do cliente Blazor Web App (BlazorWebAppEntra.Client)

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

Se o usuário precisar fazer login ou logout durante a renderização do lado do cliente, uma recarga de página inteira será iniciada.

Projeto de API web backend (MinimalApiJwt)

O projeto MinimalApiJwt é uma API web de back-end para vários projetos de frontend. O projeto configura um endpoint de API Minimal para dados meteorológicos. As solicitações do projeto do lado do servidor Blazor Web App (BlazorWebAppEntra) são intermediadas 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 é 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 e a UI 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 Program do projeto.

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 de API Web de back-end (MinimalApiJwt)

Configure o MinimalApiJwt projeto durante a JwtBearerOptions chamada no arquivo de AddJwtBearer no 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 configura a Autoridade para fazer chamadas OIDC.

jwtOptions.Authority = "{AUTHORITY}";

Os exemplos a seguir usam uma ID de locatário de aaaabbbb-0000-cccc-1111-dddd2222eeee.

Se a aplicação estiver registada num tenant ME-ID, a autoridade deve corresponder ao emissor (iss) do JWT retornado pelo fornecedor de identidade:

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

Se a aplicação estiver registada em um inquilino B2C do AAD:

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

Audience define a Audiência para qualquer token de acesso JWT recebido.

jwtOptions.Audience = "{AUDIENCE}";

Corresponda o valor exclusivamente ao caminho do URI de ID da Aplicação configurado ao adicionar o escopo Weather.Get na seção Expor uma API no portal do Entra ou do 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 segundo exemplo usa um domínio de locatário de contoso.onmicrosoft.com.

ME-ID exemplo de inquilino:

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

Exemplo de inquilino B2C do Azure AD:

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

Configurar o projeto de servidor (BlazorWebAppEntra)

AddMicrosoftIdentityWebApp da Microsoft Identity Web (Microsoft.Identity.Web pacote NuGet, documentação da API) é configurado no arquivo do BlazorWebAppEntraProgram projeto.

Obtenha o ID da aplicação (cliente), o domínio do inquilino e o ID do diretório (inquilino) a partir do registo da aplicação no portal do Entra ou do Azure. O URI do ID da aplicação é obtido para o Weather.Get escopo a partir do registo da API web. Não inclua o nome do escopo ao obter o URI da ID da aplicação no portal.

No arquivo BlazorWebAppEntra do projeto Program, forneça os valores para as seguintes variáveis de substituição na configuração Identity Web 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();

Marcadores de posição na configuração anterior:

  • {CLIENT ID (BLAZOR APP)}: O ID do aplicativo (cliente).
  • {DIRECTORY NAME}: O nome do diretório do domínio do inquilino (editor).
  • {TENANT ID}: O ID do diretório (inquilino).
  • {BASE ADDRESS}: O endereço base da API da Web.
  • {APP ID URI}: O URI do ID do aplicativo para escopos da API Web. Um dos dois seguintes formatos é usado, onde o espaço reservado {CLIENT ID (WEB API)} é o Client Id do registo Entra da API Web e o espaço reservado {DIRECTORY NAME} é o nome do diretório do domínio do tenant (editores) (exemplo: contoso).
    • ME-ID formato do locatário: api://{CLIENT ID (WEB API)}
    • Formato de locatário B2C: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

Exemplo:

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

Advertência

As aplicações de produção devem usar um provedor de cache de token distribuído de produção. Caso contrário, o aplicativo pode ter um desempenho ruim em alguns cenários. Para obter mais informações, veja a seção Utilização de um fornecedor de cache distribuído de produção para tokens.

O caminho do retorno de chamada (CallbackPath) deve corresponder ao URI de redirecionamento (caminho de retorno de chamada do início de sessão) configurado ao registar a aplicação no portal Entra ou Azure. Os caminhos são configurados na secção Autenticação do registo 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).

O SignedOutCallbackPath é o caminho de solicitação dentro do caminho base do aplicativo intercetado pelo manipulador 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 intercetar a solicitação, o manipulador OpenID Connect redireciona para o SignedOutRedirectUri ou RedirectUri, se especificado.

Configure o caminho de retorno de chamada de sessão terminada no registo do Entra da aplicação. No portal do Entra ou do Azure, defina o caminho nas entradas de URI de redirecionamento da configuração da plataforma Web.

https://localhost/signout-callback-oidc

Observação

Uma porta não é necessária para endereços localhost ao usar o Entra.

Se não adicionares o URI do caminho de retorno de chamada após a desconexão ao registo da aplicação no Entra, o Entra recusa-se a redirecionar o utilizador de volta para a aplicação e apenas pede que ele feche a janela do navegador.

Observação

O Entra não redireciona um usuário administrador principal (conta root) ou usuário externo de volta para o aplicativo Blazor. Em vez disso, o Entra desconecta o usuário do aplicativo e recomenda que ele feche todas as janelas do navegador. Para obter mais informações, consulte postLogoutRedirectUri não está funcionando quando a url da autoridade contém uma ID de locatário (AzureAD/microsoft-authentication-library-for-js #5783).

Advertência

Não armazene segredos de aplicativos, cadeias de conexão, credenciais, senhas, números de identificação pessoal (PINs), código C#/.NET privado ou chaves/tokens privados no código do lado do cliente, que é sempre inseguro. Em ambientes de teste/preparação e produção, o código Blazor do lado do servidor e as APIs da Web devem usar fluxos de autenticação seguros que evitem 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 Secret Manager é recomendada para proteger dados confidenciais. Para obter mais informações, consulte Manter com segurança dados confidenciais e credenciais.

Definir o segredo de cliente

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

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

  • ferramenta Secret Manager: A ferramenta Secret Manager armazena dados privados na máquina local e só é usada 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 programadores preferem usar repositórios de chaves para implantações em teste e produção e usar a ferramenta Secret Manager para desenvolvimento local.

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

Ferramenta Secret Manager

A ferramenta Secret Manager pode armazenar o segredo do cliente do aplicativo de servidor sob a chave de configuração AzureAd:ClientSecret.

O Blazor aplicativo de servidor não foi inicializado para a ferramenta Gerenciador Secreto. Use um shell de comando, como o shell de comando do Developer PowerShell no Visual Studio, para executar o seguinte comando. Antes de executar o comando, altere o diretório com o comando cd para o diretório do projeto de servidor. O comando estabelece um identificador de segredos de usuário (<UserSecretsId>) no arquivo de projeto do aplicativo de servidor, que é usado internamente pelas ferramentas para rastrear segredos para o aplicativo:

dotnet user-secrets init

Execute o seguinte comando para definir o segredo do cliente. O marcador de posição {SECRET} é o segredo do cliente obtido no registo Entra da aplicação:

dotnet user-secrets set "AzureAd:ClientSecret" "{SECRET}"

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

Azure Key Vault

Azure Key Vault fornece uma abordagem segura para disponibilizar o segredo do cliente da aplicação à própria aplicação.

Para criar um cofre de chaves e definir um segredo do cliente, consulte Sobre segredos do Cofre de Chaves do Azure (documentação do Azure), que vincula recursos para começar a usar o Cofre de Chaves do Azure. 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 secreto é "BlazorWebAppEntraClientSecret."

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

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

  • Crie um Gestor Identity do Azure (ou adicione uma função ao Gestor Identity existente que planeias usar) com a função de Usuário de Segredos do Cofre de Chaves. Atribua o Gerenciado Identity ao Serviço de Aplicativo do Azure que está hospedando a implantação: Configurações>Identity>Adição atribuída> pelo usuário.

    Observação

    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 do desenvolvedor no Controle de Acesso (IAM) com a função Usuário de Segredos do Cofre de Chaves . Se você quiser usar a CLI do Azure por meio do Visual Studio, execute o az login comando no painel Developer PowerShell 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 secreto (exemplo: "BlazorWebAppEntraClientSecret") do Azure ao criar o cofre de chaves e o segredo.

Importante

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

Adicione a seguinte classe AzureHelper ao projeto de servidor. O método GetKeyVaultSecret recupera um segredo de um repositório 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;
    }
}

Observação

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

Onde os serviços são registrados no arquivo de Program do projeto de servidor, obtenha e aplique o segredo do cliente usando o seguinte código:

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 tiverem sido definidos, ligue GetKeyVaultSecret para receber e atribuir o segredo do cliente da aplicação.

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

{MANAGED IDENTITY CLIENT ID}: A ID do Cliente Gerenciado Identity do Azure (GUID).

{TENANT ID}: O ID do diretório (inquilino). Exemplo: aaaabbbb-0000-cccc-1111-dddd2222eeee

{VAULT URI}: URI do cofre de chaves. Inclua a barra final ao 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 em desenvolvimento, appsettings.Staging.json durante o preparo e appsettings.Production.json para a implantação de produção. Para obter mais informações, consulte configuração do ASP.NET Core Blazor.

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

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

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

Os projetos de solução de exemplo configuram a autenticação de portador da Microsoft Identity Web e JWT em seus Program arquivos para tornar as definições de configuração detetáveis usando o preenchimento automático em 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 dos arquivos de configuração do aplicativo appsettings.json/appsettings.{ENVIRONMENT}.json, em que o espaço reservado {ENVIRONMENT} é o ambiente de tempo de execução do aplicativo. Siga as orientações nesta seção para usar os arquivos de configurações do 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 espaços reservados na configuração anterior para que correspondam aos valores que a aplicação utiliza no arquivo Program.

  • {CLIENT ID (BLAZOR APP)}: O ID do aplicativo (cliente).
  • {DIRECTORY NAME}: O nome do diretório do domínio do inquilino (editor).
  • {TENANT ID}: O ID do diretório (inquilino).
  • {BASE ADDRESS}: O endereço base da API da Web.
  • {APP ID URI}: O URI do ID do aplicativo para escopos da API Web. Um dos dois seguintes formatos é usado, onde o espaço reservado {CLIENT ID (WEB API)} é o Client Id do registo Entra da API Web e o espaço reservado {DIRECTORY NAME} é o nome do diretório do domínio do tenant (editores) (exemplo: contoso).
    • ME-ID formato do locatário: api://{CLIENT ID (WEB API)}
    • Formato de locatário B2C: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

Exemplo:

"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 aos valores personalizados/não padrão usados no Program arquivo.

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

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

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

Observação

As aplicações de produção devem usar um provedor de cache de token distribuído de produção. Caso contrário, o aplicativo pode ter um desempenho ruim em alguns cenários. Para obter mais informações, veja a seção Utilização de um fornecedor de cache distribuído de produção para tokens.

No projeto MinimalApiJwt, adicione a seguinte configuração de definições da aplicação ao ficheiro 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 que correspondam aos valores que a aplicação utiliza no arquivo Program.

  • {TENANT ID (WEB API)}: O ID do inquilino da API web.
  • {APP ID URI (WEB API)}: O URI do ID da aplicação da API Web.

Os formatos das autoridades adotam os seguintes padrões:

  • ME-ID tipo de inquilino: https://sts.windows.net/{TENANT ID}/
  • Tipo de inquilino B2C: https://login.microsoftonline.com/{TENANT ID}/v2.0/

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

  • ME-ID tipo de inquilino: api://{CLIENT ID}
  • Tipo de inquilino B2C: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID}

A configuração é reconhecida automaticamente pelo gerador de autenticação de portador JWT.

Remova as seguintes linhas do Program arquivo:

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

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

Usar um provedor de cache de token distribuído de produção

Os caches de token distribuído na memória são criados durante a chamada AddDistributedTokenCaches para garantir que haja uma implementação base disponível para o cache de token distribuído.

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).

Observação

Para desenvolvimento local e teste em uma única máquina, você pode usar caches de token na memória em vez de caches de token distribuído:

builder.Services.AddInMemoryTokenCaches();

Mais tarde, durante o período de desenvolvimento e teste, adote um provedor de cache distribuído de tokens em produção.

AddDistributedMemoryCache adiciona uma implementação padrão de IDistributedCache que armazena itens de cache na memória, sendo esta utilizada pela Microsoft Identity Web para o armazenamento em cache de tokens.

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 para o Microsoft.Extensions.Caching.Memory pacote NuGet.

Observação

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

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

Advertência

Substitua sempre os caches de tokens distribuídos em memória por um provedor real de cache de tokens ao implementar a aplicação num 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 aos aplicativos ASP.NET Core, que configuram caches distribuídos via AddDistributedMemoryCache, não AddDistributedTokenCache.

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

Observação

Para desenvolvimento inicial e testes locais em uma única máquina, você pode definir Encrypt para false e configurar um anel de chaves compartilhado de Proteção de Dados mais tarde.

options.Encrypt = false;

Mais tarde, no período de desenvolvimento e teste, habilite a criptografia de token e adote um chaveiro compartilhado de Proteção de Dados.

O exemplo a seguir mostra como usar o Armazenamento de Blobs do Azure e o Cofre de Chaves do Azure (PersistKeysToAzureBlobStorage/ProtectKeysWithAzureKeyVault) para o conjunto de chaves 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 práticas recomendadas usando os conjuntos de documentação dedicados dos serviços do Azure, que são vinculados no final desta seção.

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

Observação

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

Observação

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, dentro 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 orientações em Provedores de armazenamento de chaves no ASP.NET Core.

Configure o Azure Key Vault para criptografar as chaves de proteção de dados em repouso. Siga as orientações em Configurar ASP.NET Proteção de Dados Principais.

Use o seguinte código no arquivo onde os Program serviços estã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. Basta confirmar se todas as implantações de aplicativos usam o mesmo valor.

{MANAGED IDENTITY CLIENT ID}: A ID do Cliente Gerenciado Identity do Azure (GUID).

{TENANT ID}: ID do inquilino.

{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 utilize uma SAS.

{KEY IDENTIFIER}: Identificador de chave do Azure Key Vault usado para criptografia de chave. Uma política de acesso permite que o aplicativo aceda o cofre de chaves com Get, Unwrap Key e permissões Wrap Key. A versão da chave é obtida a partir da chave no portal do Entra ou do Azure após a sua criação. Se ativar a rotação automática da chave no cofre de chaves, certifique-se de usar um identificador de chave sem versão na configuração do cofre de chaves do aplicativo, em que nenhum GUID de chave é colocado no final do identificador (exemplo: https://contoso.vault.azure.net/keys/data-protection).

Observação

Em ambientes que não são de produção, o exemplo anterior usa DefaultAzureCredential para simplificar a autenticação ao desenvolver aplicativos que implantam 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 ao sistema.

Como alternativa, você pode configurar o aplicativo para fornecer os valores dos arquivos de configurações do 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"
}

Observação

O identificador de chave no exemplo anterior não tem versão. Não há nenhuma versão da chave GUID no final do identificador. Isto é particularmente importante se optar por configurar a rotação automática da chave. Para obter mais informações, consulte Configurar a rotação automática de chaves criptográficas no Cofre de Chaves do Azure: política de rotação de chaves.

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

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 no arquivo em que os serviços estão configurados Program.

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 conjunto de chaves e provedores de armazenamento de chaves de proteção de dados compartilhados, consulte os seguintes recursos:

Prefixo de destino do encaminhador YARP

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

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

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

Nome correspondente do projeto no ficheiro Program do projeto Aspire App Host (Aspire.AppHost):

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

Não há necessidade de alterar o prefixo de destino do encaminhador YARP ao implantar o Blazor Web App para produção. O pacote Microsoft Identity Web Downstream API usa o URI base passado via configuração para fazer a chamada de API da Web a partir do ServerWeatherForecaster, não do prefixo de destino do encaminhador YARP. Na produção, o encaminhador YARP apenas transforma a solicitação, adicionando o token de acesso do usuário.

Redirecionar para a página inicial ao sair

O componente LogInOrOut (Layout/LogInOrOut.razor) define um campo oculto para o URL de retorno (ReturnUrl) para o 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 logout. Se o usuário fizer logout de uma página segura, ele será retornado à mesma página segura e enviado de volta através 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>

Segurança dos dados meteorológicos

Para obter mais informações sobre como esta aplicação protege os seus dados meteorológicos, consulte Dados seguros em Blazor Web Apps com renderização automática interativa.

Diagnosticar problemas

Registo

O aplicativo de servidor é um aplicativo ASP.NET Core padrão. Consulte as diretrizes de registo do ASP.NET Core para habilitar um nível de registo inferior no aplicativo de servidor.

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

Erros comuns

  • O depurador interrompe numa exceção durante a finalização de sessão com o Microsoft Entra ID Externa

    A seguinte exceção interrompe o depurador do Visual Studio durante o logout com a Microsoft Entra External ID:

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

    Interrupção do depurador do Visual Studio devido a uma exceção JavaScript durante o logout

    A exceção é lançada pelo código JavaScript do Entra, portanto não representa um problema com o ASP.NET Core. A exceção não afeta a funcionalidade do aplicativo na produção, portanto, a exceção pode ser ignorada durante os testes de desenvolvimento local.

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

    Os erros mais comuns são causados por configuração incorreta. Seguem-se 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 que um aplicativo autentique clientes.
    • Escopos de solicitação incorretos impedem que os clientes acessem os endpoints 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 a aplicação numa porta diferente daquela configurada no URI de redirecionamento do registo da aplicação 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 de porta do aplicativo e a porta onde o aplicativo está sendo executado devem corresponder para endereços nãolocalhost.

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

    Se a configuração parecer correta:

    • Analise os logs do aplicativo.

    • 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 IP ou aplicativo de servidor depois de fazer uma solicitação. A orientação das ferramentas de desenvolvedor pode ser encontrada nos seguintes artigos:

    A equipa de documentação responde a comentários e sinais de erro nos artigos (abrir um problema na secção de comentários Esta página), mas não pode fornecer suporte de produto. Vários fóruns públicos de suporte estão disponíveis para ajudar na solução de problemas de um aplicativo. Recomendamos o seguinte:

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

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

  • Cliente não autorizado para ME-ID

    info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] A autorização falhou. Estes requisitos não foram atendidos: DenyAnonymousAuthorizationRequirement: Requer um usuário autenticado.

    Erro no retorno da chamada de início de sessão 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, aceda ao manifesto do aplicativo .
    2. Defina o atributo allowPublicClient como null ou true.

Cookies e dados do site

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

  • Cookies de login do utilizador
  • Cookies de aplicações
  • Dados do site em cache e armazenados

Uma abordagem para evitar que cookies persistentes e dados do site interfiram com testes e solução de problemas é:

  • Configurar um navegador
    • Use um navegador para testar que consigas configurar para excluir todos cookie e os dados do site sempre que fechares o navegador.
    • Verifique se o navegador está fechado manualmente ou pelo IDE para qualquer alteração na configuração do aplicativo, do usuário de teste ou do provedor.
  • Use um comando personalizado para abrir um navegador no modo InPrivate ou de navegação anônima no Visual Studio:
    • Abra a caixa de diálogo "Procurar com" a partir do botão Executar do Visual Studio.
    • Selecione o botão Adicionar.
    • Forneça o caminho para o seu navegador no campo do programa. Os seguintes caminhos executáveis são locais de instalação típicos para o Windows 10. Se o seu navegador estiver instalado em um local diferente ou você não estiver usando o 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 no modo incógnito. Alguns navegadores exigem o URL do aplicativo.
      • Microsoft Edge: Utilize -inprivate.
      • Google Chrome: Use o --incognito --new-window {URL}, onde o placeholder {URL} é o URL a ser aberto (por exemplo, https://localhost:5001).
      • Mozilla Firefox: Use -private -url {URL}, onde o marcador de posição {URL} é o URL a ser aberto (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 ter que selecionar o perfil do navegador para cada iteração de teste com uma aplicação, defina o perfil como predefinido com o botão Definir como predefinido.
    • Certifique-se de que o navegador está fechado pelo IDE para qualquer alteração na configuração do aplicativo, usuário de teste ou provedor.

Atualizações de aplicativos

Um aplicativo em funcionamento pode falhar imediatamente após atualizar o SDK do .NET Core na máquina de desenvolvimento ou alterar as versões do pacote no aplicativo. Em alguns casos, pacotes incoerentes podem quebrar um aplicativo ao executar grandes atualizações. A maioria desses problemas pode ser corrigida seguindo estas instruções:

  1. Limpe os caches de pacotes NuGet do sistema local executando dotnet nuget locals all --clear a partir 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.

Observação

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 acerca de um pacote, utilize a Galeria NuGet.

Inicie a solução a partir do projeto correto

Blazor Web Apps:

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

Blazor Server:

Inicie a solução a partir do 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