Ajouter une authentification Microsoft Entra ID à une application .NET Aspire

Ce guide montre comment sécuriser une application distribuée .NET Aspire avec Microsoft Entra ID pour l’authentification et l’autorisation. Il couvre :

  1. Frontend Serveur Blazor (MyService.Web) : connexion d'utilisateur avec OpenID Connect et acquisition de jetons
  2. Protéger le serveur principal de l’API (MyService.ApiService) : validation JWT à l’aide de Microsoft. Identity.Web
  3. Flux de bout en bout : Blazor acquiert des jetons d’accès et appelle l’API protégée avec la découverte de service Aspire.

Ce guide part du principe que vous avez démarré avec un projet Aspire créé à l’aide de la commande suivante :

aspire new aspire-starter --name MyService

Prerequisites

Conseil / Astuce

Est-ce la première fois que vous utilisez Aspire ? Consultez .NET Aspire vue d’ensemble.

Comprendre le flux de travail en deux phases

Ce guide suit une approche en deux phases :

Étape Que se passe-t-il ? Résultat
Phase 1 Ajouter du code d’authentification avec des valeurs de substitution Les builds de l'application, mais ne s'exécute pas
Phase 2 Configurer les inscriptions d’applications Microsoft Entra L’application fonctionne avec une authentification réelle

Inscrire des applications dans Microsoft Entra ID

Pour que votre application puisse authentifier les utilisateurs, vous avez besoin de deux inscriptions d’application dans Microsoft Entra :

Enregistrement de l’application Objectif Configuration des touches
API (MyService.ApiService) Valide les jetons entrants URI d’ID d’application, access_as_user étendue
Application web (MyService.Web) Connecte des utilisateurs, acquiert des jetons URI de redirection, secret client, autorisations d’API

Si vous avez déjà configuré des enregistrements d’applications, vous avez besoin de ces valeurs pour votre appsettings.json:

  • TenantId — ID de locataire Microsoft Entra
  • API ClientId — ID d’application (client) de votre inscription d’application API
  • URI d’ID d’application API — Généralement api://<api-client-id> (utilisé dans Audiences et Scopes)
  • Id client de l'application web — ID d'application (client) de votre enregistrement d'application web
  • Clé secrète client (ou certificat) : informations d’identification pour l’application web (stocker dans des secrets utilisateur, et non appsettings.json)
  • Étendues — Les étendues que votre application web demande, par exemple, api://<api-client-id>/.default ou api://<api-client-id>/access_as_user

Étape 1 : Inscrire l’API

  1. Accédez à centre d’administration Microsoft Entra>Identity>Applications>inscriptions d'applications.
  2. Sélectionnez Nouvelle inscription.
    • Nom :MyService.ApiService
    • Types de comptes pris en charge : Comptes dans cet annuaire organisationnel uniquement (locataire unique)
    • Sélectionnez Inscription.
  3. Accédez à Exposer une API>Ajouter en regard de l’URI d’ID d’application.
    • Acceptez la valeur par défaut (api://<client-id>) ou personnalisez-la.
    • Sélectionnez Ajouter une étendue :
      • Nom de l’étendue :access_as_user
      • Qui peut donner son consentement : Administrateurs et utilisateurs
      • Nom d’affichage du consentement administrateur : Accéder à l’API MyService
      • Description du consentement de l’administrateur : Permet à l’application d’accéder à l’API MyService pour le compte de l’utilisateur connecté.
      • Sélectionnez Ajouter une étendue.
  4. Copiez l’ID d’application (client) : vous en aurez besoin pour les deux appsettings.json fichiers.

Pour plus d’informations, consultez Démarrage rapide : Configurer une application pour exposer une API web.

Étape 2 : Inscrire l’application web

  1. Accédez à inscriptions d'applications>New registration.
    • Nom :MyService.Web
    • Types de comptes pris en charge : Comptes dans cet annuaire organisationnel uniquement
    • URI de redirection : Sélectionnez Web et entrez l’URL de votre application + /signin-oidc
      • Pour le développement local : https://localhost:7001/signin-oidc (vérifiez votre launchSettings.json pour le port réel)
    • Sélectionnez Inscription.
  2. Accédez à l’authentification>Ajouter un URI pour ajouter toutes vos URL de développement (à partir de launchSettings.json).
  3. Accédez à Certificats et secrets>Secrets client>Nouveau secret client.
    • Ajoutez une description et une expiration.
    • Copiez immédiatement la valeur du secret . Elle ne s’affiche pas à nouveau.
  4. Accédez aux autorisations> d’APIAjouter une autorisation>Mes API.
    • Sélectionnez MyService.ApiService.
    • Sélectionnez access_as_user>Ajouter des autorisations.
    • Sélectionnez Accorder le consentement de l’administrateur pour [locataire] (ou les utilisateurs sont invités à utiliser à la première utilisation).
  5. Copiez l’ID d’application (client) pour l’application appsettings.jsonweb.

Note

Certaines organisations n’autorisent pas les secrets client. Pour obtenir des alternatives, consultez Informations d'identification par certificat ou l'Authentification sans certificat.

Pour plus d’informations, consultez Démarrage rapide : Inscrire une application.

Étape 3 : Mettre à jour la configuration

Après avoir créé les enregistrements d’applications, mettez à jour vos appsettings.json fichiers :

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

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

Stockez le secret de manière sécurisée :

cd MyService.Web
dotnet user-secrets set "AzureAd:ClientCredentials:0:ClientSecret" "YOUR_SECRET_VALUE"
Valeur Où trouver
TenantId Centre d'administration Microsoft Entra > Aperçu > ID de locataire
API ClientId Inscriptions d'application > MyService.ApiService > ID d'application (client)
Web ClientId ID d'inscriptions d'applications > MyService.Web > Application (client)
Client Secret Créé à l’étape 2 (copie immédiatement lors de la création)

Note

Le modèle de démarrage Aspire crée automatiquement une WeatherApiClient classe dans le MyService.Web projet. Ce HttpClient typé est utilisé tout au long de ce guide pour illustrer l’appel de l’API protégée. Vous n’avez pas besoin de créer cette classe vous-même : elle fait partie du modèle.


Prise en main rapide

Cette section fournit une référence condensée pour l’ajout d’authentification. Pour obtenir des procédures pas à pas détaillées, consultez la partie 1 et la partie 2.

API (MyService.ApiService)

Installez le package NuGet Microsoft.Identity.Web :

dotnet add package Microsoft.Identity.Web

Ajoutez la configuration Microsoft Entra à appsettings.json :

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

Inscrivez l’authentification et l’autorisation dans Program.cs:

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

Application web (MyService.Web)

Installez le paquet NuGet Microsoft.Identity.Web :

dotnet add package Microsoft.Identity.Web

Ajoutez la configuration Microsoft Entra à 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"] }
}

Configurez l’authentification, l’acquisition de jetons et le client d’API en aval dans 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();

Le MicrosoftIdentityMessageHandler acquiert des jetons et les attache automatiquement, et BlazorAuthenticationChallengeHandler gère les défis de consentement et d’accès conditionnel.

Important

N’oubliez pas de créer UserInfo.razor pour le bouton de connexion. Pour plus d’informations, consultez Ajouter des composants d’interface utilisateur Blazor .

Note

BlazorAuthenticationChallengeHandler et LoginLogoutEndpointRouteBuilderExtensions sont disponibles dans Microsoft.Identity.Web (v3.3.0+). Aucune copie de fichier n’est requise.


Identifier les fichiers à modifier

Le tableau suivant répertorie les fichiers que vous modifiez dans chaque projet :

Projet Fichier Modifications
ApiService Program.cs Authentification JWT Bearer, intergiciel d’autorisation
appsettings.json configuration de Microsoft Entra
.csproj Ajouter Microsoft.Identity.Web
Web Program.cs Authentification OIDC, acquisition de jetons, BlazorAuthenticationChallengeHandler
appsettings.json Microsoft Entra configuration, étendues d’API en aval
.csproj Ajouter Microsoft.Identity.Web (v3.3.0+)
Components/UserInfo.razor Interface utilisateur du bouton connexion (nouveau fichier)
Components/Layout/MainLayout.razor Inclure le composant UserInfo
Components/Routes.razor AuthorizeRouteView pour les pages protégées
Pages appelant des APIs Essayer/intercepter avec ChallengeHandler

Comprendre le flux d’authentification

Le diagramme suivant montre comment le front-end Blazor, Microsoft Entra et l’API protégée interagissent :

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. L’utilisateur visite l’application Blazor → Non authentifié → voit le bouton « Connexion ».
  2. Utilisateur sélectionne login → Redirige vers /authentication/login → défi OIDC → Microsoft Entra.
  3. Utilisateur se connecte → Microsoft Entra redirige vers /signin-oidc → cookie établi.
  4. L’utilisateur accède à la page Météo → Blazor appelle WeatherApiClient.GetAsync().
  5. MicrosoftIdentityMessageHandler intercepte la requête, acquiert un jeton à partir du cache (ou actualise silencieusement) et attache l’en-tête Authorization: Bearer <token> .
  6. API reçoit la demande → Microsoft. Identity.Web valide le → JWT retourne des données.
  7. Blazor affiche les données météorologiques.

Passer en revue la structure de la solution

Le modèle de démarrage Aspire crée la disposition de projet suivante :

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

Partie 1 : Sécuriser le back-end d’API avec Microsoft. Identity.Web

Cette section configure le projet d’API pour valider les jetons du porteur JWT émis par Microsoft Entra.

Ajouter le package Microsoft.Identity.Web

Exécutez la commande suivante pour installer le package NuGet Microsoft.Identity.Web :

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

Configurer les paramètres de Microsoft Entra

Ajoutez la configuration Microsoft Entra à 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": "*"
}

Propriétés de clé :

  • ClientId : ID d’inscription d’application API Microsoft Entra
  • TenantId : votre ID de locataire Microsoft Entra, ou "organizations" pour un locataire multi-tenant, ou "common" pour n’importe quel compte Microsoft
  • Audiences: audiences valides du jeton (généralement l'URI de l'ID de votre application)

Mettre à jour le Program.cs de l’API

Remplacez le contenu de MyService.ApiService/Program.cs par le code suivant pour ajouter l'authentification JWT Bearer et protéger les 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);
}

Modifications clés :

  • Enregistrer l’authentification JWT Bearer avec AddMicrosoftIdentityWebApi
  • Ajouter app.UseAuthentication() et app.UseAuthorization() intergiciel
  • Appliquer .RequireAuthorization() aux points de terminaison protégés

Tester l’API protégée

Vérifiez que l’API rejette les demandes non authentifiées et accepte des jetons valides.

Envoyez une requête sans jeton :

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

Envoyez une demande avec un jeton valide :

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

Partie 2 : Configurer le front-end Blazor pour l’authentification

L’application Blazor Server utilise Microsoft. Identity.Web à :

  • Connecter des utilisateurs avec OIDC
  • Acquérir des jetons d’accès pour appeler l’API
  • Attacher des jetons aux requêtes HTTP sortantes

Ajouter le package Microsoft.Identity.Web

Exécutez la commande suivante pour installer le package NuGet Microsoft.Identity.Web :

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

Configurer les paramètres de Microsoft Entra

Ajoutez la configuration Microsoft Entra et les étendues d’API en aval à 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": "*"
}

Détails de la configuration :

  • ClientId: ID d’inscription d’application web (et non l’ID d’API)
  • ClientCredentials: Identifiants de l’application web pour acquérir des jetons. Prend en charge plusieurs types d’informations d’identification. Consultez la vue d'ensemble des identifiants pour connaître les options prêtes à l'emploi pour la production.
  • Scopes: doit correspondre à l’URI d’ID d’application de l’API avec /.default le suffixe

Avertissement

Pour la production, utilisez des certificats ou une identité managée au lieu de secrets clients. Consultez l’authentification sans certificat pour l’approche recommandée.

Mettre à jour la Program.cs de l’application web

Remplacez le contenu de MyService.Web/Program.cs par le code suivant pour configurer l’authentification OIDC, l’acquisition de jetons et le client API en aval :

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

Points clés :

  • AddMicrosoftIdentityWebApp: configure l’authentification OIDC
  • EnableTokenAcquisitionToCallDownstreamApi: active l’acquisition de jetons pour les API en aval
  • AddScoped<BlazorAuthenticationChallengeHandler>: gère le consentement incrémentiel et l’accès conditionnel dans Blazor Server
  • AddMicrosoftIdentityMessageHandler: attache automatiquement des jetons porteurs aux requêtes HttpClient
  • https+http://apiservice : La découverte du service Aspire traduit cela en l'URL réelle de l'API
  • Ordre des intergiciels : UseAuthentication()UseAuthorization() → points de terminaison

L’extension AddMicrosoftIdentityMessageHandler prend en charge plusieurs modèles de configuration :

Option 1 : Configuration à partir de appsettings.json (indiqué précédemment)

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

Option 2 : Configuration inline avec délégué d’action

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

Option 3 : Configuration par requête (sans paramètre)

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

Ajouter des composants d’interface utilisateur Blazor

Important

Cette étape est fréquemment oubliée. Sans le composant UserInfo, les utilisateurs n’ont aucun moyen de se connecter.

BlazorAuthenticationChallengeHandler et LoginLogoutEndpointRouteBuilderExtensions sont inclus dans Microsoft.Identity.Web v3.3.0+. Elles sont automatiquement disponibles une fois que vous référencez le package : aucune copie de fichier n’est requise.

Créer 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>

Ajouter à la disposition : Inclure <UserInfo /> dans votre 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>

Mettre à jour Routes.razor pour AuthorizeRouteView

Remplacez RouteView par AuthorizeRouteView dans 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>

Gérer les exceptions sur les pages appelant des API

Blazor Server nécessite une gestion explicite des exceptions pour l’accès conditionnel et le consentement. Vous devez gérer MicrosoftIdentityWebChallengeUserException sur chaque page qui appelle une API en aval, sauf si votre application est pré-autorisée et que vous demandez toutes les étendues à l’avance dans Program.cs.

L’exemple suivant Weather.razor illustre la gestion appropriée des exceptions :

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

Le modèle fonctionne comme suit :

  1. IsAuthenticatedAsync() vérifie si l’utilisateur est connecté avant d’effectuer des appels d’API.
  2. HandleExceptionAsync() intercepte MicrosoftIdentityWebChallengeUserException (ou InnerException).
  3. S’il s’agit d’une exception de défi, l’utilisateur est redirigé pour s’authentifier à nouveau avec les revendications ou étendues requises.
  4. S’il ne s’agit pas d’une exception de défi, HandleExceptionAsync retourne false afin que vous puissiez gérer l’erreur vous-même.

Stocker le secret client dans les secrets utilisateur

Utilisez le gestionnaire de secrets .NET pour stocker la clé secrète client en toute sécurité pendant le développement.

Avertissement

Ne validez jamais les secrets dans le système de contrôle de version.

Initialisez les secrets des utilisateurs et stockez le secret client.

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

Ensuite, mettez à jour appsettings.json pour supprimer le secret codé en dur :

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

Microsoft. Identity.Web prend en charge plusieurs types d’informations d’identification. Pour la production, consultez Vue d’ensemble des informations d’identification.


Vérifier l’implémentation

Utilisez cette liste de contrôle pour confirmer que vous avez effectué toutes les étapes requises.

Projet d’API

  • [ ] Ajout du package Microsoft.Identity.Web
  • [ ] Mise à jour appsettings.json avec la section AzureAd
  • [ ] Mis à jour Program.cs avec AddMicrosoftIdentityWebApi
  • [ ] Ajouté .RequireAuthorization() aux points de terminaison protégés

Projet Web/Blazor

  • [ ] Ajout du package Microsoft.Identity.Web (v3.3.0+)
  • [ ] Mise à jour appsettings.json avec les sections AzureAd et WeatherApi
  • [ ] Mis à jour Program.cs avec OIDC, acquisition de jetons
  • [ ] Ajouté AddScoped<BlazorAuthenticationChallengeHandler>()
  • [ ] Créé Components/UserInfo.razor (bouton de connexion)
  • [ ] Mise à jour MainLayout.razor pour inclure <UserInfo />
  • [ ] Mis à jour Routes.razor avec AuthorizeRouteView
  • [ ] Ajout d'un try/catch avec ChallengeHandler sur chaque page appelant des API
  • [ ] Clé secrète client stockée dans les secrets utilisateur

Vérification

  • [ ] dotnet build réussit
  • [ ] Créations d'applications dans le centre d'administration Microsoft Entra
  • [ ] appsettings.json a des GUID réels (pas de texte substitut)

Tester et résoudre les problèmes

Une fois l’implémentation terminée, exécutez l’application et vérifiez le flux d’authentification de bout en bout.

Exécuter l’application

Démarrez Aspire AppHost pour lancer les projets web et API :

# From solution root
dotnet restore
dotnet build

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

Tester le flux d’authentification

  1. Ouvrez le navigateur → interface utilisateur web Blazor (consultez le tableau de bord Aspire pour l’URL).
  2. Sélectionnez Login → Connectez-vous avec Microsoft Entra.
  3. Accédez à la page Météo .
  4. Vérifiez les chargements de données météorologiques (à partir de l’API protégée).

Résoudre les problèmes courants

Le tableau suivant répertorie les problèmes fréquents et leurs solutions :

Problème Solution
401 sur les appels d’API Vérifiez que les étendues en appsettings.json correspondent à l’URI de l’ID d’application de l’API
Échec de la redirection OIDC Ajouter /signin-oidc aux URI de redirection Microsoft Entra
Jeton non attaché Assurez-vous que AddMicrosoftIdentityMessageHandler soit appelée sur le HttpClient
Échec de la découverte de service Vérifiez si les AppHost.cs références des deux projets sont en cours d'exécution.
AADSTS65001 Consentement administrateur requis : accorder le consentement dans le centre d’administration Microsoft Entra
Aucun bouton de connexion Vérifiez UserInfo.razor qu’il existe et qu’il est inclus dans MainLayout.razor
Boucle de consentement Vérifiez que try/catch avec HandleExceptionAsync se trouve sur toutes les pages d’appel d’API

Activer la journalisation MSAL

Lors de la résolution des problèmes d’authentification, activez la journalisation MSAL détaillée pour afficher les détails de l’acquisition de jetons. Ajoutez les niveaux de log suivants à appsettings.json:

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

Avertissement

Désactivez la journalisation de débogage en production, car elle peut être très détaillée.

Inspecter les jetons

Pour déboguer les problèmes de jeton, décodez votre JWT à jwt.ms et vérifiez :

  • aud (audience) : correspond à l’ID client ou à l’URI d’ID d’application de votre API
  • iss (émetteur) : correspond à votre entité cliente (https://login.microsoftonline.com/<tenant-id>/v2.0)
  • scp (étendues) : contient les étendues requises
  • exp (expiration) : Le jeton n’a pas expiré

Explorer les scénarios courants

Les sections suivantes montrent comment étendre l’implémentation de base pour des cas d’usage supplémentaires.

Protéger les pages Blazor

Ajoutez l’attribut aux pages qui nécessitent l’authentification [Authorize] :

@page "/weather"
@attribute [Authorize]

Ou définissez des stratégies d’autorisation dans Program.cs:

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

Valider les périmètres dans l’API

Vérifiez que l’API accepte uniquement les jetons avec des étendues spécifiques en chaînant RequireScope:

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

Utiliser des jetons d’application uniquement (service à service)

Pour les scénarios de démon ou les appels de service à service sans contexte utilisateur, définissez RequestAppToken sur 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;
});

Utiliser des informations d’identification sans certificat pour la production

Pour les déploiements de production dans Azure, utilisez l’identité managée au lieu des secrets clients. Configurez la ClientCredentials section comme suit :

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

Pour plus d’informations, consultez l’authentification sans certificat.

Appeler des API en aval à partir de l’API (on-behalf-of)

Si votre API doit appeler une autre API en aval pour le compte de l’utilisateur, activez l’acquisition de jetons pour le compte dans 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"));

Ajoutez la configuration de l’API en aval à appsettings.json:

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

Appelez ensuite l’API en aval à partir d’un point de terminaison :

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

Pour plus d’informations, consultez Appeler des API en aval.