Adicionar autenticação Microsoft Entra ID a uma aplicação .NET Aspire

Este guia mostra como proteger uma aplicação distribuída .NET Aspire com autenticação e autorização Microsoft Entra ID. Cobre:

  1. Interface Blazor Server (MyService.Web): Login do utilizador com OpenID Connect e aquisição de tokens
  2. Protected API backend (MyService.ApiService): validação JWT usando Microsoft. Identity.Web
  3. Fluxo de ponta a ponta: Blazor adquire tokens de acesso e chama a API protegida com a descoberta do serviço Aspire

Este guia assume que começou com um projeto Aspire criado usando o seguinte comando:

aspire new aspire-starter --name MyService

Pré-requisitos

Sugestão

Novo na Aspire? Veja a visão geral de .NET Aspire.

Compreenda o fluxo de trabalho em duas fases

Este guia segue uma abordagem em duas fases:

Fase O que acontece Result
Fase 1 Adicionar código de autenticação com valores provisórios A aplicação compila mas não executa
Fase 2 Disponibilizar registos de aplicações Microsoft Entra A aplicação corre com autenticação real

Registe aplicações no Microsoft Entra ID

Antes de a sua aplicação autenticar utilizadores, precisa de dois registos na Microsoft Entra:

Registo de Aplicações Purpose Configuração das chaves
API (MyService.ApiService) Valida tokens recebidos URI de ID da Aplicação, access_as_user âmbito
Aplicação Web (MyService.Web) Regista utilizadores, adquire tokens URIs de redirecionamento, segredo do cliente, permissões da API

Se já tem os registos da aplicação configurados, precisa destes valores para o seu appsettings.json:

  • TenantId — O seu ID de inquilino Microsoft Entra
  • API ClientId — ID da aplicação (cliente) do registo da sua aplicação API
  • API ID da aplicação URI — Normalmente api://<api-client-id> (usado em Audiences e Scopes)
  • Web App ClientId — ID da aplicação (cliente) do registo da sua aplicação web
  • Segredo do Cliente (ou certificado) — Credencial para a aplicação web (armazenar em segredos do utilizador, não appsettings.json)
  • Escopos — O(s) âmbito(s) que a sua aplicação web solicita, por exemplo, api://<api-client-id>/.default ou api://<api-client-id>/access_as_user

Passo 1: Registar a API

  1. Vá a centro de administração Microsoft Entra>Identidade>Aplicações>Registros de Apps.
  2. Selecione Novo registo.
    • Designação:MyService.ApiService
    • Tipos de conta suportados: Contas apenas neste diretório organizacional (Inquilino único)
    • Selecione Register.
  3. Vá a Expor uma API>Adicionar junto ao URI de ID da Aplicação.
    • Aceita o padrão (api://<client-id>) ou personaliza-o.
    • Selecionar Adicionar um telescópio:
      • Nome do âmbito:access_as_user
      • Quem pode consentir: Administradores e utilizadores
      • Nome de visualização para consentimento do administrador: Aceder à API MyService
      • Descrição do consentimento do administrador: Permite que a aplicação aceda à API MyService em nome do utilizador iniciado sessão.
      • Selecione Adicionar escopo.
  4. Copie o ID da Aplicação (cliente) — vai precisar disto para ambos appsettings.json os ficheiros.

Para mais informações, consulte Quickstart: Configurar uma aplicação para expor uma API web.

Passo 2: Registar a aplicação web

  1. Vá a Registos de aplicações>Novo registo.
    • Designação:MyService.Web
    • Tipos de conta suportados: Apenas contas neste diretório organizacional
    • Redirecionar URI: Selecione Web e introduza a URL da sua aplicação + /signin-oidc
      • Para desenvolvimento local: https://localhost:7001/signin-oidc (verifica o porto propriamente dito launchSettings.json )
    • Selecione Register.
  2. Vá a Autenticação>Adicionar URI para adicionar todos os seus URLs de desenvolvimento (de launchSettings.json).
  3. Vá para Certificados & segredos>Segredos do cliente>Novo segredo do cliente.
    • Adicione uma descrição e um prazo de validade.
    • Copie imediatamente o valor secreto — não será mostrado novamente.
  4. Vá a permissões API>Adicionar uma permissão>Minhas APIs.
    • Selecione MyService.ApiService.
    • Selecionar access_as_user>Adicionar permissões.
    • Selecione Conceder consentimento de administrador para [inquilino] (ou os utilizadores são solicitados na primeira utilização).
  5. Copie o ID de aplicação (cliente) para a aplicação web appsettings.json.

Observação

Algumas organizações não permitem segredos de clientes. Para alternativas, veja Credenciais de Certificado ou Autenticação Sem Certificado.

Para mais informações, consulte Quickstart: Registar uma candidatura.

Passo 3: Atualizar configuração

Depois de criar os registos da aplicação, atualize os seus appsettings.json ficheiros:

API (MyService.ApiService/appsettings.json):

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "YOUR_TENANT_ID",
    "ClientId": "YOUR_API_CLIENT_ID",
    "Audiences": ["api://YOUR_API_CLIENT_ID"]
  }
}

Aplicação Web (MyService.Web/appsettings.json):

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "YOUR_TENANT_ID",
    "ClientId": "YOUR_WEB_CLIENT_ID",
    "CallbackPath": "/signin-oidc",
    "ClientCredentials": [
      { "SourceType": "ClientSecret" }
    ]
  },
  "WeatherApi": {
    "Scopes": ["api://YOUR_API_CLIENT_ID/.default"]
  }
}

Guarde o segredo de forma segura:

cd MyService.Web
dotnet user-secrets set "AzureAd:ClientCredentials:0:ClientSecret" "YOUR_SECRET_VALUE"
Valor Onde encontrar
TenantId centro de administração Microsoft Entra > Visão Geral > ID do Inquilino
API ClientId Registos da aplicação > MyService.ApiService > ID da aplicação (cliente)
Web ClientId Registos de aplicações > MyService.Web > ID da aplicação (cliente)
Client Secret Criado no Passo 2 (copiar imediatamente assim que criado)

Observação

O modelo inicial do Aspire cria automaticamente uma WeatherApiClient classe no MyService.Web projeto. Este HttpClient tipado é utilizado ao longo deste guia para demonstrar como chamar a API protegida. Não precisas de criar esta classe tu próprio — faz parte do modelo.


Comece rapidamente

Esta secção fornece uma referência condensada para adicionar autenticação. Para guias detalhadas, consulte a Parte 1 e a Parte 2.

API (MyService.ApiService)

Instalar o pacote NuGet Microsoft.Identity.Web:

dotnet add package Microsoft.Identity.Web

Adicione a configuração Microsoft Entra a appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<tenant-id>",
    "ClientId": "<api-client-id>",
    "Audiences": ["api://<api-client-id>"]
  }
}

Registo de autenticação e autorização em Program.cs:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddAuthorization();
// ...
app.UseAuthentication();
app.UseAuthorization();
// ...
app.MapGet("/weatherforecast", () => { /* ... */ }).RequireAuthorization();

Aplicação Web (MyService.Web)

Instala o pacote Microsoft.Identity.Web do NuGet:

dotnet add package Microsoft.Identity.Web

Adicione a configuração Microsoft Entra a appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<tenant-id>",
    "ClientId": "<web-client-id>",
    "CallbackPath": "/signin-oidc",
    "ClientCredentials": [{ "SourceType": "ClientSecret" }]
  },
  "WeatherApi": { "Scopes": ["api://<api-client-id>/.default"] }
}

Configure a autenticação, a obtenção de tokens e o cliente de API a jusante em Program.cs:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<BlazorAuthenticationChallengeHandler>();

builder.Services.AddHttpClient<WeatherApiClient>(client =>
    client.BaseAddress = new("https+http://apiservice"))
    .AddMicrosoftIdentityMessageHandler(builder.Configuration.GetSection("WeatherApi"));
// ...
app.UseAuthentication();
app.UseAuthorization();
app.MapGroup("/authentication").MapLoginAndLogout();

Adquire e anexa automaticamente tokens, e o MicrosoftIdentityMessageHandler trata de desafios de consentimento e acesso condicional.

Importante

Não te esqueças de criar o UserInfo.razor para o botão de login. Consulte Adicionar componentes da interface do Blazor para mais detalhes.

Observação

BlazorAuthenticationChallengeHandler e LoginLogoutEndpointRouteBuilderExtensions são incluídos no Microsoft.Identity.Web (v3.3.0+). Não é necessário copiar ficheiros.


Identificar ficheiros a modificar

A tabela seguinte lista os ficheiros que altera em cada projeto:

Projeto Ficheiro Changes
ApiService Program.cs Autenticação JWT Bearer, middleware de autorização
appsettings.json Microsoft Entra configuration
.csproj Adicionar Microsoft.Identity.Web
Sítio Web Program.cs Autenticação OIDC, aquisição de tokens, BlazorAuthenticationChallengeHandler
appsettings.json configuração do Microsoft Entra, escopos de API a jusante
.csproj Adicionar Microsoft.Identity.Web (v3.3.0+)
Components/UserInfo.razor Interface do botão de login (novo ficheiro)
Components/Layout/MainLayout.razor Incluir o componente UserInfo
Components/Routes.razor AuthorizeRouteView para páginas protegidas
Páginas que chamam APIs Tentar/apanhar com o ChallengeHandler

Compreender o fluxo de autenticação

O diagrama seguinte mostra como a interface Blazor, a Microsoft Entra e a API protegida interagem:

flowchart LR
  A[User Browser] -->|1 Login OIDC| B[Blazor Server<br/>MyService.Web]
  B -->|2 Redirect| C[Microsoft Entra ID]
  C -->|3 auth code| B
  B -->|4 exchange auth code| C
  C -->|5 tokens| B
  B -->|6 cookie + session| A
  B -->|7 HTTP + Bearer token| D[ASP.NET API<br/>MyService.ApiService<br/>Microsoft.Identity.Web]
  D -->|8 Validate JWT| C
  D -->|9 Weather data| B
  1. O utilizador visita a aplicação Blazor → Não autenticado → vê o botão "Login".
  2. O utilizador seleciona Iniciar Sessão → Redireciona para /authentication/login → desafio OIDC → Microsoft Entra.
  3. Utilizador inicia sessão → Microsoft Entra redireciona para /signin-oidc → cookie criado.
  4. O utilizador navega até à página de Meteorologia → Blazor chama WeatherApiClient.GetAsync().
  5. MicrosoftIdentityMessageHandler interceta o pedido, adquire um token da cache (ou atualiza silenciosamente) e anexa o Authorization: Bearer <token> cabeçalho.
  6. API recebe o pedido → Microsoft. O Identity.Web valida o JWT → devolve os dados.
  7. Blazor renderiza dados meteorológicos.

Rever a estrutura da solução

O modelo inicial do Aspire cria o seguinte layout de projeto:

MyService/
├── MyService.AppHost/           # Aspire orchestration
├── MyService.ApiService/        # Protected API (Microsoft.Identity.Web)
├── MyService.Web/               # Blazor Server (Microsoft.Identity.Web)
├── MyService.ServiceDefaults/   # Shared defaults
└── MyService.Tests/             # Tests

Parte 1: Proteger o backend da API com a Microsoft. Identity.Web

Esta secção configura o projeto API para validar tokens JWT Bearer emitidos pela Microsoft Entra.

Adicione o pacote Microsoft.Identity.Web

Execute o seguinte comando para instalar o pacote NuGet Microsoft.Identity.Web:

cd MyService.ApiService
dotnet add package Microsoft.Identity.Web

Configurar as definições do Microsoft Entra

Adicione a configuração Microsoft Entra a MyService.ApiService/appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<your-tenant-id>",
    "ClientId": "<your-api-client-id>",
    "Audiences": [
      "api://<your-api-client-id>"
    ]
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Propriedades principais:

  • ClientId: ID de registo da aplicação API Microsoft Entra
  • TenantId: O seu ID de locatário Microsoft Entra, ou "organizations" para multi-locatário, ou "common" para qualquer conta Microsoft
  • Audiences: Audiências válidas de tokens (tipicamente o URI do seu ID da Aplicação)

Atualizar API Program.cs

Substitua o conteúdo de MyService.ApiService/Program.cs pelo seguinte código para adicionar autenticação JWT Bearer e proteger os endpoints:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// Add Microsoft.Identity.Web JWT Bearer authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddProblemDetails();
builder.Services.AddOpenApi();
builder.Services.AddAuthorization();

var app = builder.Build();

app.UseExceptionHandler();
app.UseAuthentication();
app.UseAuthorization();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

string[] summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild",
    "Warm", "Balmy", "Hot", "Sweltering", "Scorching"];

app.MapGet("/", () =>
    "API service is running. Navigate to /weatherforecast to see sample data.");

app.MapGet("/weatherforecast", () =>
{
    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;
})
.WithName("GetWeatherForecast")
.RequireAuthorization();

app.MapDefaultEndpoints();
app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

Alterações principais:

  • Registar autenticação do portador JWT com AddMicrosoftIdentityWebApi
  • Adicionar app.UseAuthentication() e app.UseAuthorization() middleware
  • Aplicar .RequireAuthorization() em endpoints protegidos

Teste a API protegida

Verifique se a API rejeita pedidos não autenticados e aceita tokens válidos.

Enviar um pedido sem token:

curl https://localhost:<PORT>/weatherforecast
# Expected: 401 Unauthorized

Envie um pedido com um token válido:

curl -H "Authorization: Bearer <TOKEN>" https://localhost:<PORT>/weatherforecast
# Expected: 200 OK with weather data

Parte 2: Configurar a interface Blazor para autenticação

A aplicação Blazor Server usa Microsoft. Identity.Web para:

  • Autenticar utilizadores com OIDC
  • Adquirir tokens de acesso para chamar a API
  • Anexar tokens a pedidos HTTP de saída

Adicione o pacote Microsoft.Identity.Web

Execute o seguinte comando para instalar o pacote NuGet Microsoft.Identity.Web:

cd MyService.Web
dotnet add package Microsoft.Identity.Web

Configurar as definições do Microsoft Entra

Adicione os escopos de configuração do Microsoft Entra e da API a jusante a MyService.Web/appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<your-tenant>.onmicrosoft.com",
    "TenantId": "<tenant-guid>",
    "ClientId":  "<web-app-client-id>",
    "CallbackPath": "/signin-oidc",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "<your-client-secret>"
      }
    ]
  },
  "WeatherApi": {
    "Scopes": [ "api://<api-client-id>/.default" ]
  },
  "Logging": {
    "LogLevel":  {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Detalhes de configuração:

  • ClientId: ID de registo da aplicação web (não o ID da API)
  • ClientCredentials: Credenciais para a aplicação web adquirir tokens. Suporta múltiplos tipos de credenciais. Consulte a visão geral das Credenciais para opções prontas para produção.
  • Scopes: Deve corresponder ao URI do ID da Aplicação da API com o sufixo /.default

Advertência

Para produção, utilize certificados ou identidade gerida em vez de segredos do cliente. Consulte Autenticação sem certificado para a abordagem recomendada.

Atualizar a aplicação web Program.cs

Substitua o conteúdo de MyService.Web/Program.cs pelo seguinte código para configurar a autenticação OIDC, aquisição de tokens e o cliente API a jusante:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;
using MyService.Web;
using MyService.Web.Components;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// Authentication + Microsoft Identity Web
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddCascadingAuthenticationState();

// Blazor components
builder.Services.AddRazorComponents().AddInteractiveServerComponents();

// Blazor authentication challenge handler for incremental consent and Conditional Access
builder.Services.AddScoped<BlazorAuthenticationChallengeHandler>();

builder.Services.AddOutputCache();

// Downstream API client with MicrosoftIdentityMessageHandler
builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
    // Aspire service discovery: resolves "apiservice" at runtime
    client.BaseAddress = new("https+http://apiservice");
})
.AddMicrosoftIdentityMessageHandler(builder.Configuration.GetSection("WeatherApi"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.UseOutputCache();

app.MapStaticAssets();
app.MapRazorComponents<App>()
   .AddInteractiveServerRenderMode();

// Login/Logout endpoints with incremental consent support
app.MapGroup("/authentication").MapLoginAndLogout();

app.MapDefaultEndpoints();
app.Run();

Pontos principais:

  • AddMicrosoftIdentityWebApp: Configura autenticação OIDC
  • EnableTokenAcquisitionToCallDownstreamApi: Permite aquisição de tokens para APIs a jusante
  • AddScoped<BlazorAuthenticationChallengeHandler>: Trata do consentimento incremental e do acesso condicional no Blazor Server
  • AddMicrosoftIdentityMessageHandler: Anexa automaticamente os tokens de autenticação a pedidos HttpClient
  • https+http://apiservice: O Aspire Service Discovery resolve isto para a URL real da API
  • Ordem do middleware: UseAuthentication()UseAuthorization() → endpoints

A AddMicrosoftIdentityMessageHandler extensão suporta múltiplos padrões de configuração:

Opção 1: Configuração a partir de appsettings.json (mostrada anteriormente)

.AddMicrosoftIdentityMessageHandler(builder.Configuration.GetSection("WeatherApi"));

Opção 2: Configuração em linha com delegado de ação

.AddMicrosoftIdentityMessageHandler(options =>
{
    options.Scopes.Add("api://<api-client-id>/.default");
});

Opção 3: Configuração por pedido (sem parâmetros)

.AddMicrosoftIdentityMessageHandler();

// Then in your service, configure per-request:
var request = new HttpRequestMessage(HttpMethod.Get, "/weatherforecast")
    .WithAuthenticationOptions(options =>
    {
        options.Scopes.Add("api://<api-client-id>/.default");
    });
var response = await _httpClient.SendAsync(request);

Adicionar componentes da interface do Blazor

Importante

Este passo é frequentemente esquecido. Sem o componente UserInfo, os utilizadores não têm forma de iniciar sessão.

BlazorAuthenticationChallengeHandler e LoginLogoutEndpointRouteBuilderExtensions são incluídos em Microsoft.Identity.Web v3.3.0+. Estão automaticamente disponíveis assim que consultas o pacote — não é necessário copiar ficheiros.

Criar MyService.Web/Components/UserInfo.razor:

@using Microsoft.AspNetCore.Components.Authorization

<AuthorizeView>
    <Authorized>
        <span class="nav-item">Hello, @context.User.Identity?.Name</span>
        <form action="/authentication/logout" method="post" class="nav-item">
            <AntiforgeryToken />
            <input type="hidden" name="returnUrl" value="/" />
            <button type="submit" class="btn btn-link nav-link">Logout</button>
        </form>
    </Authorized>
    <NotAuthorized>
        <a href="/authentication/login?returnUrl=/" class="nav-link">Login</a>
    </NotAuthorized>
</AuthorizeView>

Adicionar ao layout: Inclua <UserInfo /> no seu MainLayout.razor:

@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <UserInfo />
        </div>

        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

Atualize o Routes.razor para o AuthorizeRouteView

Substituir RouteView por AuthorizeRouteView em Components/Routes.razor:

@using Microsoft.AspNetCore.Components.Authorization

<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
            <NotAuthorized>
                <p>You are not authorized to view this page.</p>
                <a href="/authentication/login">Login</a>
            </NotAuthorized>
        </AuthorizeRouteView>
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
</Router>

Tratar exceções em páginas que chamam APIs

O Blazor Server requer tratamento explícito de exceções para Acesso Condicional e consentimento. Deve tratar MicrosoftIdentityWebChallengeUserException em todas as páginas que chamam uma API downstream, a menos que a sua aplicação esteja pré-autorizada e solicite todos os scopes antecipadamente em Program.cs.

O exemplo seguinte Weather.razor demonstra o tratamento correto de exceções:

@page "/weather"
@attribute [Authorize]

@using Microsoft.AspNetCore.Authorization
@using Microsoft.Identity.Web

@inject WeatherApiClient WeatherApi
@inject BlazorAuthenticationChallengeHandler ChallengeHandler

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

@if (!string.IsNullOrEmpty(errorMessage))
{
    <div class="alert alert-warning">@errorMessage</div>
}
else if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;
    private string? errorMessage;

    protected override async Task OnInitializedAsync()
    {
        if (!await ChallengeHandler.IsAuthenticatedAsync())
        {
            await ChallengeHandler.ChallengeUserWithConfiguredScopesAsync("WeatherApi:Scopes");
            return;
        }

        try
        {
            forecasts = await WeatherApi.GetWeatherAsync();
        }
        catch (Exception ex)
        {
            // Handle incremental consent / Conditional Access
            if (!await ChallengeHandler.HandleExceptionAsync(ex))
            {
                errorMessage = $"Error loading weather data: {ex.Message}";
            }
        }
    }
}

O padrão funciona da seguinte forma:

  1. IsAuthenticatedAsync() verifica se o utilizador está com sessão iniciada antes de fazer chamadas para a API.
  2. HandleExceptionAsync() captura MicrosoftIdentityWebChallengeUserException (ou como InnerException).
  3. Caso seja uma exceção de contestação, o utilizador é redirecionado para reautenticar com as declarações ou escopos exigidos.
  4. Se não for uma exceção de desafio, HandleExceptionAsync devolve false para que possas tratar do erro por ti próprio.

Guardar o segredo do cliente nos segredos do utilizador

Use o .NET Secret Manager para armazenar o segredo do cliente de forma segura durante o desenvolvimento.

Atenção

Nunca armazene segredos no controlo de código fonte.

Inicialize os segredos do utilizador e armazene o segredo do cliente:

cd MyService.Web
dotnet user-secrets init
dotnet user-secrets set "AzureAd:ClientCredentials:0:ClientSecret" "<your-client-secret>"

Depois atualiza appsettings.json para remover o segredo codificado fixamente:

{
  "AzureAd": {
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret"
      }
    ]
  }
}

Microsoft. O Identity.Web suporta múltiplos tipos de credenciais. Para a utilização em produção, consulte Visão Geral das Credenciais.


Verificar a implementação

Use esta lista de verificação para confirmar que completou todos os passos exigidos.

Projeto API

  • [ ] Adicionar Microsoft.Identity.Web pacote
  • [ ] Atualizado appsettings.json com a secção AzureAd
  • [ ] Atualizado Program.cs com AddMicrosoftIdentityWebApi
  • [ ] Adicionado .RequireAuthorization() aos endpoints protegidos

Projeto Web/Blazor

  • [ ] Adicionado Microsoft.Identity.Web pacote (v3.3.0+)
  • [ ] Atualizado appsettings.json com as secções AzureAd e WeatherApi
  • [ ] Atualizado Program.cs com OIDC, obtenção de tokens
  • [ ] Adicionado AddScoped<BlazorAuthenticationChallengeHandler>()
  • [ ] Foi criado Components/UserInfo.razor (o botão de login)
  • [ ] Foi atualizado MainLayout.razor para incluir <UserInfo />
  • [ ] Atualizado Routes.razor com AuthorizeRouteView
  • [ ] Adicionado try/catch com ChallengeHandler em cada página que chama APIs
  • [ ] Guardava o segredo do cliente em segredos de utilizador

Verificação

  • [ ] dotnet build consegue
  • [ ] Registos de aplicações criados no centro de administração do Microsoft Entra
  • [ ] appsettings.json tem GUIDs reais (sem marcadores de lugar)

Teste e solução de problemas

Depois de concluir a implementação, execute a aplicação e verifique o fluxo de autenticação de ponta a ponta.

Execute o aplicativo

Inicie o Aspire AppHost para lançar tanto os projetos web como API:

# From solution root
dotnet restore
dotnet build

# Launch AppHost (starts both Web and API)
dotnet run --project .\MyService.AppHost\MyService.AppHost.csproj

Teste o fluxo de autenticação

  1. Abrir o navegador → Blazor Web UI (consulte o painel Aspire para URL).
  2. Selecione Login → Iniciar sessão com Microsoft Entra.
  3. Navegar até à página do Tempo .
  4. Verifique as cargas de dados meteorológicos (a partir da API protegida).

Resolver Problemas Comuns

A tabela seguinte lista problemas frequentes e as suas soluções:

Issue Solução
401 em chamadas API Verifique se os escopos em appsettings.json correspondem ao ID da App URI da API
Erro no redirecionamento OIDC Adicionar /signin-oidc às URIs de redirecionamento da Microsoft Entra
Token não anexado Garantir que AddMicrosoftIdentityMessageHandler seja chamado em HttpClient
Falhas na descoberta do serviço Verifica AppHost.cs referências de ambos os projetos e confirma que estão a funcionar.
AADSTS65001 Consentimento do administrador necessário — conceda consentimento no centro de administração Microsoft Entra
Sem botão de login Certifique-se de que UserInfo.razor exista e esteja incluído em MainLayout.razor
Ciclo de consentimento Garante que o try/catch com HandleExceptionAsync está em todas as páginas de chamada API

Ativar registo de MSAL

Ao resolver problemas de autenticação, ative o registo detalhado do MSAL para ver os detalhes da aquisição de tokens. Adicione os seguintes níveis logarítmicos a appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.Identity": "Debug",
      "Microsoft.IdentityModel": "Debug"
    }
  }
}

Advertência

Desative o registo de depuração em produção porque pode ser muito verboso.

Inspeção dos tokens

Para depurar problemas de tokens, descodifique o seu JWT no jwt.ms e verifique:

  • aud (audiência): Corresponde ao ID do Cliente ou URI do ID da Aplicação da sua API
  • iss (emissor): Corresponde ao seu inquilino (https://login.microsoftonline.com/<tenant-id>/v2.0)
  • scp (escopos): Contém os escopos necessários
  • exp (expiração): O token não expirou

Explore cenários comuns

As secções seguintes mostram como estender a implementação base para casos de uso adicionais.

Proteger as páginas do Blazor

Adicione o [Authorize] atributo às páginas que requerem autenticação:

@page "/weather"
@attribute [Authorize]

Ou definir políticas de autorização em Program.cs:

// Program.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
});
@attribute [Authorize(Policy = "AdminOnly")]

Validar os escopos na API

Garanta que a API só aceita tokens com escopos específicos, encadeando RequireScope:

app.MapGet("/weatherforecast", () =>
{
    // ... implementation
})
.RequireAuthorization()
.RequireScope("access_as_user");

Use tokens de aplicação apenas (serviço para serviço)

Para cenários de daemon ou chamadas service-to-service sem contexto de utilizador, defina RequestAppToken para true:

builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
    client.BaseAddress = new("https+http://apiservice");
})
.AddMicrosoftIdentityMessageHandler(options =>
{
    options.Scopes.Add("api://<api-client-id>/.default");
    options.RequestAppToken = true;
});

Use credenciais sem necessidade de certificado para produção

Para implementações em produção no Azure, use identidade gerida em vez de segredos do cliente. Configure a ClientCredentials secção da seguinte forma:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<tenant-guid>",
    "ClientId":  "<web-app-client-id>",
    "ClientCredentials": [
      {
        "SourceType": "SignedAssertionFromManagedIdentity",
        "ManagedIdentityClientId": "<user-assigned-mi-client-id>"
      }
    ]
  }
}

Para mais informações, consulte Autenticação sem certificado.

Chamar APIs a jusante a partir da API (em nome de)

Se a sua API precisar de chamar outra API a jusante em nome do utilizador, ative a aquisição de tokens em nome deste em Program.cs:

// MyService.ApiService/Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddDownstreamApi("GraphApi", builder.Configuration.GetSection("GraphApi"));

Adicione a configuração da API a jusante para appsettings.json:

{
  "GraphApi": {
    "BaseUrl": "https://graph.microsoft.com/v1.0",
    "Scopes": [ "User.Read" ]
  }
}

Depois, faça uma chamada à API subsequente a partir de um ponto de extremidade:

{
    var user = await downstreamApi.GetForUserAsync<JsonElement>("GraphApi", "me");
    return user;
}).RequireAuthorization();

Para mais informações, consulte Chamada de APIs a jusante.