Agregar autenticación de Microsoft Entra ID a una aplicación .NET Aspire

En esta guía se muestra cómo proteger una aplicación distribuida .NET Aspire con Microsoft Entra ID autenticación y autorización. Abarca:

  1. Front-end de Blazor Server (MyService.Web): inicio de sesión de usuario con OpenID Connect y adquisición de tokens
  2. Backend de API protegida (MyService.ApiService): validación de JWT mediante Microsoft.Identity.Web
  3. Flujo integral: Blazor adquiere tokens de acceso y llama a la API protegida con el descubrimiento de servicios Aspire

En esta guía se supone que ha iniciado con un proyecto Aspire creado mediante el siguiente comando:

aspire new aspire-starter --name MyService

Prerrequisitos

  • SDK de .NET 9 o versiones posteriores
  • .NET Aspire CLI - Consulte Instalar Aspire CLI
  • Microsoft Entra tenant — Consulte Registrar aplicaciones en Microsoft Entra ID para la configuración

Sugerencia

¿No está familiarizado con Aspire? Consulte visión general de .NET Aspire.

Descripción del flujo de trabajo en dos fases

En esta guía se sigue un enfoque de dos fases:

Fase ¿Qué ocurre? Resultado
Fase 1 Agregar código de autenticación con valores de marcador de posición Compilación de aplicación, pero no se ejecuta
Fase 2 Configurar registros de aplicaciones de Microsoft Entra La aplicación se ejecuta con autenticación real

Registro de aplicaciones en Microsoft Entra ID

Para que la aplicación pueda autenticar a los usuarios, necesita dos registros de aplicaciones en Microsoft Entra:

Registro de aplicaciones propósito Configuración de claves
API (MyService.ApiService) Valida los tokens entrantes. URI de id. de aplicación, access_as_user ámbito
Aplicación web (MyService.Web) Inicia sesión de usuarios y adquiere tokens URI de redirección, secreto de cliente, permisos de API

Si ya tiene registros de aplicaciones configurados, necesita estos valores para appsettings.json:

  • TenantId: identificador de inquilino de Microsoft Entra
  • ClientId de API — Identificación de la aplicación (cliente) del registro de la aplicación de API
  • URI de id. de aplicación de API : normalmente api://<api-client-id> (se usa en Audiences y Scopes)
  • ID de la aplicación web — ID de la aplicación (cliente) de la aplicación web
  • Secreto de cliente (o certificado): credencial para la aplicación web (almacén en secretos de usuario, no appsettings.json)
  • Ámbitos : los ámbitos que solicita la aplicación web, por ejemplo, api://<api-client-id>/.default o api://<api-client-id>/access_as_user

Paso 1: Registro de la API

  1. Vaya al Centro de administración de Microsoft Entra>Identity (Identidad)>Applications (Aplicaciones)>Registros de aplicaciones (Registros de aplicaciones).
  2. Seleccione Nuevo registro.
    • Nombre:MyService.ApiService
    • Tipos de cuenta admitidos: Solo las cuentas de este directorio organizativo (inquilino único)
    • Seleccione Registrar.
  3. Vaya a Exponer una API>Agregar junto a URI de identificador de aplicación.
    • Acepte el valor predeterminado (api://<client-id>) o personalícelo.
    • Seleccione Agregar un ámbito:
      • Nombre del ámbito:access_as_user
      • Quién puede dar su consentimiento: Administradores y usuarios
      • Nombre para mostrar del consentimiento del administrador: Acceso a MyService API
      • Descripción del consentimiento del administrador: Permite que la aplicación acceda a MyService API en nombre del usuario que ha iniciado sesión.
      • Selecciona la opción Agregar un ámbito.
  4. Copie el identificador de aplicación (cliente): necesitará esto para ambos appsettings.json archivos.

Para más información, consulte Inicio rápido: Configuración de una aplicación para exponer una API web.

Paso 2: Registrar la aplicación web

  1. Vaya a Registros de aplicaciones>Nuevo registro.
    • Nombre:MyService.Web
    • Tipos de cuenta admitidos: Solo las cuentas de este directorio organizativo
    • URI de redirección: Seleccione Web y escriba la dirección URL de la aplicación + /signin-oidc
      • Para el desarrollo local: https://localhost:7001/signin-oidc (verifique launchSettings.json para el puerto real)
    • Seleccione Registrar.
  2. Vaya a Autenticación>Agregar URI para agregar todas las direcciones URL de desarrollo (desde launchSettings.json).
  3. Vaya a Certificados y secretos>Secretos de cliente>Nuevo secreto de cliente.
    • Agregue una descripción y una expiración.
    • Copie el valor del secreto inmediatamente; no se volverá a mostrar.
  4. Vaya a Permisos> de APIAgregar un permiso>Mis API.
    • Seleccione MyService.ApiService.
    • Seleccione access_as_user>Agregar permisos.
    • Seleccione Conceder consentimiento del administrador para [inquilino] (o se solicita a los usuarios al primer uso).
  5. Copie el identificador de aplicación (cliente) para la aplicación web.appsettings.json

Nota:

Algunas organizaciones no permiten secretos de cliente. Para obtener alternativas, consulte Credenciales de certificado o autenticación sin certificado.

Para obtener más información, consulte Inicio rápido: Registro de una aplicación.

Paso 3: Actualización de la configuración

Después de crear los registros de aplicaciones, actualice los appsettings.json archivos:

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"]
  }
}

Aplicación 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"]
  }
}

Almacene el secreto de forma segura:

cd MyService.Web
dotnet user-secrets set "AzureAd:ClientCredentials:0:ClientSecret" "YOUR_SECRET_VALUE"
Importancia Dónde encontrar
TenantId Centro de administración Microsoft Entra > Información general > Id. de inquilino
API ClientId Registros de aplicaciones > MyService.ApiService > ID de aplicación (cliente)
Web ClientId Registros de aplicaciones > MyService.Web > ID de aplicación (cliente)
Client Secret Creado en el paso 2 (copiar inmediatamente cuando se creó)

Nota:

La plantilla aspire starter crea automáticamente una WeatherApiClient clase en el MyService.Web proyecto. Este HttpClient tipado se utiliza a lo largo de esta guía para demostrar cómo llamar a la API protegida. No es necesario crear esta clase usted mismo, sino que forma parte de la plantilla.


Empiece rápidamente

En esta sección se proporciona una referencia condensada para agregar autenticación. Para ver tutoriales detallados, vea Parte 1 y Parte 2.

API (MyService.ApiService)

Instale el paquete NuGet Microsoft.Identity.Web:

dotnet add package Microsoft.Identity.Web

Agregue la configuración de Microsoft Entra a appsettings.json:

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

Registre la autenticación y la autorización en Program.cs:

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

Aplicación web (MyService.Web)

Instale el paquete NuGet Microsoft.Identity.Web:

dotnet add package Microsoft.Identity.Web

Agregue la configuración de 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 la autenticación, la adquisición de tokens y el cliente de API de bajada en 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();

MicrosoftIdentityMessageHandler adquiere y adjunta tokens automáticamente, mientras que BlazorAuthenticationChallengeHandler maneja los desafíos de consentimiento y acceso condicional.

Importante

No olvide crear UserInfo.razor para el botón de inicio de sesión. Consulte Adición de componentes de la interfaz de usuario de Blazor para obtener más información.

Nota:

BlazorAuthenticationChallengeHandler y LoginLogoutEndpointRouteBuilderExtensions se incluyen en Microsoft.Identity.Web (v3.3.0+). No se requiere copia de archivos.


Identificación de archivos que se van a modificar

En la tabla siguiente se enumeran los archivos que cambia en cada proyecto:

Proyecto Archivo Changes
ApiService Program.cs Autenticación JWT Bearer, intermediario de autorización
appsettings.json Configuración de Microsoft Entra
.csproj Agregar Microsoft.Identity.Web
Web Program.cs Autenticación de OIDC, adquisición de tokens, BlazorAuthenticationChallengeHandler
appsettings.json configuración de Microsoft Entra, ámbitos de API de bajada
.csproj Agregar Microsoft.Identity.Web (v3.3.0+)
Components/UserInfo.razor Interfaz de usuario del botón de inicio de sesión (nuevo archivo)
Components/Layout/MainLayout.razor Incluir componente UserInfo
Components/Routes.razor AuthorizeRouteView para páginas protegidas
Las páginas que llaman a APIs Prueba y captura con ChallengeHandler

Descripción del flujo de autenticación

En el diagrama siguiente se muestra cómo interactúa el front-end de Blazor, Microsoft Entra y la API protegida:

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. El usuario visita la aplicación Blazor → No autenticada → ve el botón "Iniciar sesión".
  2. El usuario selecciona Iniciar sesión → Redirige a /authentication/login → Desafío OIDC → Microsoft Entra.
  3. El usuario inicia sesión → Microsoft Entra redirige a /signin-oidc → cookie establecida.
  4. El usuario navega a la página Weather → Blazor llama a WeatherApiClient.GetAsync().
  5. MicrosoftIdentityMessageHandler intercepta la solicitud, adquiere un token de la memoria caché (o actualiza de forma silenciosa) y adjunta el Authorization: Bearer <token> encabezado.
  6. API recibe la solicitud → Microsoft. Identity.Web valida el → JWT devuelve datos.
  7. Blazor representa los datos meteorológicos.

Revisión de la estructura de la solución

La plantilla aspire starter crea el siguiente diseño de proyecto:

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: Protección del back-end de api con Microsoft. Identity.Web

En esta sección se configura el proyecto de API para validar los tokens de portador JWT emitidos por Microsoft Entra.

Agregue el paquete Microsoft.Identity.Web

Ejecute el siguiente comando para instalar el Microsoft. Paquete NuGet Identity.Web:

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

Configuración de Microsoft Entra

Agregue la configuración de 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": "*"
}

Propiedades clave:

  • ClientId: ID de registro de aplicación de API de Microsoft Entra
  • TenantId: identificador de tenant de Microsoft Entra o "organizations" para multi-tenant o "common" para cualquier cuenta de Microsoft
  • Audiences: Destinatarios de tokens válidos (normalmente el URI de ID de aplicación)

Actualizar el archivo Program.cs de la API

Reemplace el contenido de MyService.ApiService/Program.cs por el código siguiente para agregar la autenticación de portador JWT y proteger los puntos de conexión:

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

Cambios clave:

  • Registrar la autenticación de tipo Bearer JWT con AddMicrosoftIdentityWebApi
  • Agregar app.UseAuthentication() y app.UseAuthorization() middleware
  • Aplicar .RequireAuthorization() a puntos de conexión protegidos

Prueba de la API protegida

Compruebe que la API rechaza solicitudes no autenticadas y acepta tokens válidos.

Envíe una solicitud sin un token:

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

Envíe una solicitud con un token válido:

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

Parte 2: Configuración del front-end de Blazor para la autenticación

La aplicación Blazor Server usa Microsoft. Identity.Web a:

  • Autenticar usuarios con OIDC
  • Adquisición de tokens de acceso para llamar a la API
  • Adjunte tokens a las solicitudes HTTP salientes

Agregue el paquete Microsoft.Identity.Web

Ejecute el siguiente comando para instalar el Microsoft. Paquete NuGet Identity.Web:

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

Configurar ajustes de Microsoft Entra

Agregue la configuración de Microsoft Entra y los ámbitos de API descendente 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": "*"
}

Detalles de configuración:

  • ClientId: id. de registro de aplicación web (no el identificador de API)
  • ClientCredentials: credenciales de la aplicación web para adquirir tokens. Admite varios tipos de credenciales. Consulte Resumen de credenciales para las opciones listas para producción.
  • Scopes: debe coincidir con el URI del identificador de aplicación de la API con el sufijo /.default.

Advertencia

Para producción, use certificados o identidad administrada en lugar de secretos de cliente. Consulte Autenticación sin certificados para obtener el enfoque recomendado.

Actualizar Program.cs de la aplicación web

Reemplace el contenido de MyService.Web/Program.cs por el código siguiente para configurar la autenticación de OIDC, la adquisición de tokens y el cliente de API de bajada:

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

Puntos clave:

  • AddMicrosoftIdentityWebApp: configura la autenticación de OIDC.
  • EnableTokenAcquisitionToCallDownstreamApi: habilita la adquisición de tokens para las API de bajada.
  • AddScoped<BlazorAuthenticationChallengeHandler>: controla el consentimiento incremental y el acceso condicional en Blazor Server.
  • AddMicrosoftIdentityMessageHandler: Adjunta automáticamente tokens de portador a las solicitudes de HttpClient.
  • https+http://apiservice: La detección de servicios Aspire resuelve esto a la URL real de la API.
  • Orden de middleware: UseAuthentication()UseAuthorization() → puntos de conexión

La AddMicrosoftIdentityMessageHandler extensión admite varios patrones de configuración:

Opción 1: Configuración de appsettings.json (mostrada anteriormente)

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

Opción 2: Configuración en línea con delegado de Action

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

Opción 3: Configuración por solicitud (sin 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);

Adición de componentes de la interfaz de usuario de Blazor

Importante

Este paso se olvida con frecuencia. Sin el componente UserInfo, los usuarios no tienen forma de iniciar sesión.

BlazorAuthenticationChallengeHandler y LoginLogoutEndpointRouteBuilderExtensions se envían con Microsoft.Identity.Web v3.3.0+. Están disponibles automáticamente una vez que hace referencia al paquete; no se requiere copia de archivos.

Crear 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>

Agregar al diseño: Incluya <UserInfo /> en 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>

Actualizar Routes.razor para AuthorizeRouteView

Reemplace RouteView con AuthorizeRouteView en 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>

Control de excepciones en páginas que llaman a las API

Blazor Server requiere un control explícito de excepciones para el acceso condicional y el consentimiento. Debes controlar MicrosoftIdentityWebChallengeUserException en todas las páginas que llamen a una API descendente, a menos que la aplicación esté preautorizada y solicites todos los permisos con antelación en Program.cs.

En el ejemplo siguiente Weather.razor se muestra el control de excepciones adecuado:

@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}";
            }
        }
    }
}

El patrón funciona de la siguiente manera:

  1. IsAuthenticatedAsync() comprueba si el usuario ha iniciado sesión antes de realizar llamadas API.
  2. HandleExceptionAsync() captura MicrosoftIdentityWebChallengeUserException (o como InnerException).
  3. Si se trata de una excepción de autenticación, se redirige al usuario para volver a autenticarse con los atributos o ámbitos necesarios.
  4. Si no es una excepción de desafío, HandleExceptionAsync devuelve false para que pueda controlar el error usted mismo.

Almacenar el secreto del cliente en los secretos de usuario

Use el administrador de secretos de .NET para almacenar el secreto de cliente de forma segura durante el desarrollo.

Precaución

Nunca confirme secretos en el control de código fuente.

Inicialice los secretos de usuario y almacene el secreto de cliente:

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

A continuación, actualice appsettings.json para quitar el secreto codificado de forma codificada:

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

Microsoft. Identity.Web admite varios tipos de credenciales. Para la producción, consulte Descripción general de las credenciales.


Comprobación de la implementación

Use esta lista de comprobación para confirmar que completó todos los pasos necesarios.

Proyecto de API

  • [ ] Se ha agregado Microsoft.Identity.Web paquete
  • [ ] Actualizado appsettings.json la sección con AzureAd
  • [ ] Actualizado Program.cs con AddMicrosoftIdentityWebApi
  • [ ] Se ha agregado .RequireAuthorization() a los puntos de conexión protegidos.

Proyecto web/Blazor

  • [ ] Se ha agregado Microsoft.Identity.Web paquete (v3.3.0+)
  • [ ] Actualizado appsettings.json con las secciones AzureAd y WeatherApi
  • [ ] Actualizado Program.cs con OIDC y la adquisición de tokens
  • [ ] Elemento agregado AddScoped<BlazorAuthenticationChallengeHandler>()
  • [ ] Creó Components/UserInfo.razor (el botón de inicio de sesión)
  • [ ] Actualizado MainLayout.razor para incluir <UserInfo />
  • [ ] Actualizado Routes.razor con AuthorizeRouteView
  • [ ] Se ha agregado try/catch con ChallengeHandler en cada página que llama a las API.
  • [ ] Secreto de cliente almacenado en secretos de usuario

Comprobación

  • [ ] dotnet build tiene éxito
  • Registros de aplicaciones creadas en el centro de administración de Microsoft Entra
  • [ ] appsettings.json tiene GUID reales (sin marcadores de posición)

Prueba y solución de problemas

Después de completar la implementación, ejecute la aplicación y compruebe el flujo de autenticación de un extremo a otro.

Ejecutar la aplicación

Inicie Aspire AppHost para iniciar los proyectos web y de API:

# From solution root
dotnet restore
dotnet build

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

Prueba del flujo de autenticación

  1. Abra el explorador → interfaz de usuario web de Blazor (compruebe el panel Aspire para ver la dirección URL).
  2. Seleccione Login → Iniciar sesión con Microsoft Entra.
  3. Vaya a la página Tiempo .
  4. Compruebe las cargas de datos meteorológicos (desde la API protegida).

Resolución de problemas comunes

En la tabla siguiente se enumeran los problemas frecuentes y sus soluciones:

Cuestión Solución
Error 401 en llamadas API Verifica que los ámbitos en appsettings.json coincidan con el URI de ID de la aplicación de la API
Error de redirección de OIDC Agregar /signin-oidc a los URI de redirección de Microsoft Entra
Token no asignado Asegúrese de que se llame a AddMicrosoftIdentityMessageHandler en el HttpClient
Error en la detección de servicios Compruebe que AppHost.cs hace referencia a ambos proyectos y que se están ejecutando.
AADSTS65001 Consentimiento del administrador necesario: conceda consentimiento en el Centro de administración Microsoft Entra
No hay ningún botón de inicio de sesión Asegúrese de que UserInfo.razor existe y está incluido en MainLayout.razor
Bucle de consentimiento Asegúrese de que try/catch with HandleExceptionAsync está en todas las páginas de llamadas api

Habilita el registro de MSAL

Al solucionar problemas de autenticación, habilite el registro detallado de MSAL para ver los detalles de la adquisición de tokens. Agregue los siguientes niveles de registro a appsettings.json:

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

Advertencia

Deshabilite el registro de depuración en producción porque puede ser muy verboso.

Inspección de tokens

Para depurar problemas de token, descodifique el JWT en jwt.ms y compruebe lo siguiente:

  • aud (audiencia): coincide con el identificador de cliente de la API o el URI del identificador de aplicación.
  • iss (emisor): coincide con el inquilino (https://login.microsoftonline.com/<tenant-id>/v2.0)
  • scp (ámbitos): contiene los ámbitos necesarios.
  • exp (expiración): el token no ha expirado

Exploración de escenarios comunes

En las secciones siguientes se muestra cómo ampliar la implementación base para casos de uso adicionales.

Protección de páginas de Blazor

Agregue el [Authorize] atributo a las páginas que requieren autenticación:

@page "/weather"
@attribute [Authorize]

O bien, defina directivas de autorización en Program.cs:

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

Validación de ámbitos en la API

Asegúrese de que la API solo acepta tokens con ámbitos específicos mediante el encadenamiento RequireScope:

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

Uso de tokens exclusivamente de aplicación (servicio a servicio)

Para escenarios de demonio o llamadas de servicio a servicio sin un contexto de usuario, establezca RequestAppToken como 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;
});

Uso de credenciales sin certificado para producción

En el caso de las implementaciones de producción en Azure, use la identidad administrada en lugar de los secretos de cliente. Configure la sección ClientCredentials de la siguiente manera:

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

Para obtener más información, consulte Autenticación sin certificados.

Llamar a las API descendentes desde la API (en nombre de)

Si su API necesita llamar a otra API secundaria en nombre del usuario, habilite la adquisición de tokens en nombre de: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"));

Agregue la configuración de la API de destino a appsettings.json:

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

A continuación, llame a la API descendente desde un punto de conexión.

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

Para más información, consulte Llamada a las API de bajada.