Appeler des API en aval à partir d’applications web

Ce guide explique comment appeler des API en aval à partir d’applications web ASP.NET Core et OWIN à l’aide de Microsoft. Identity.Web. Dans les applications web, vous obtenez des jetons pour le compte de l’utilisateur connecté pour appeler des API avec des autorisations déléguées.

Comprendre le flux de jetons

Lorsqu’un utilisateur se connecte à votre application web, vous pouvez appeler des API en aval (Microsoft Graph, des services Azure ou des API personnalisées) pour leur compte. Microsoft. Identity.Web gère l’acquisition, la mise en cache et l’actualisation automatique des jetons.

Passer en revue le flux de jeton utilisateur

sequenceDiagram
    participant User as User Browser
    participant WebApp as Your Web App
    participant AzureAD as Microsoft Entra ID
    participant API as Downstream API

    User->>WebApp: 1. Access page requiring API data
    Note over WebApp: User already signed in
    WebApp->>AzureAD: 2. Request access token for API<br/>(using user's refresh token)
    AzureAD->>AzureAD: 3. Validate & check consent
    AzureAD->>WebApp: 4. Return access token
    Note over WebApp: Cache token
    WebApp->>API: 5. Call API with token
    API->>WebApp: 6. Return data
    WebApp->>User: 7. Render page with data

Passer en revue les conditions préalables requises

Vérifiez que votre environnement répond aux exigences suivantes avant de commencer.

  • Application web configurée avec l’authentification OpenID Connect
  • Connexion de l'utilisateur opérationnelle
  • Inscription d’application avec des autorisations d’API configurées
  • Consentement de l’utilisateur obtenu (ou consentement administrateur accordé)

Implémenter ASP.NET Core

1. Configurer l’authentification et l’acquisition de jetons

Ajoutez des services d’authentification et activez l’acquisition de jetons dans votre Program.cs fichier.

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

var builder = WebApplication.CreateBuilder(args);

// Add authentication with explicit scheme
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddRazorPages()
    .AddMicrosoftIdentityUI();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

var app = builder.Build();

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

app.MapRazorPages();
app.Run();

2. Configurer appsettings.json

Définissez vos paramètres d’inscription d’application Microsoft Entra ID et d’API en aval dans appsettings.json.

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",
    "CallbackPath": "/signin-oidc",
    "SignedOutCallbackPath": "/signout-callback-oidc",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "your-client-secret"
      }
    ]
  },
  "DownstreamApis": {
    "GraphAPI": {
      "BaseUrl": "https://graph.microsoft.com/v1.0",
      "Scopes": ["user.read", "mail.read"]
    },
    "MyAPI": {
      "BaseUrl": "https://myapi.example.com",
      "Scopes": ["api://my-api-id/access_as_user"]
    }
  }
}

Important: Pour les applications web appelant des API en aval, vous avez besoin d’informations d’identification client (certificat ou secret) en plus de la configuration de connexion.

3. Ajouter la prise en charge de l’API en aval

Choisissez l’une des options suivantes pour inscrire vos API en aval.

Option A : Inscrire des API nommées

Le code suivant enregistre plusieurs API en aval depuis la configuration.

using Microsoft.Identity.Web;

// Register multiple downstream APIs
builder.Services.AddDownstreamApis(
    builder.Configuration.GetSection("DownstreamApis"));

Option B : Utilisez Microsoft Graph Helper

Le code suivant enregistre le client Microsoft Graph SDK depuis la configuration.

// Install: Microsoft.Identity.Web.GraphServiceClient
builder.Services.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApis:GraphAPI"));

4. Appeler l’API en aval à partir du contrôleur

Injectez IDownstreamApi dans votre contrôleur et appelez l'API pour le compte de l'utilisateur connecté.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
public class ProfileController : Controller
{
    private readonly IDownstreamApi _downstreamApi;
    private readonly ILogger<ProfileController> _logger;

    public ProfileController(
        IDownstreamApi downstreamApi,
        ILogger<ProfileController> logger)
    {
        _downstreamApi = downstreamApi;
        _logger = logger;
    }

    public async Task<IActionResult> Index()
    {
        try
        {
            // Call downstream API on behalf of user
            var userData = await _downstreamApi.GetForUserAsync<UserData>(
                "MyAPI",
                options => options.RelativePath = "api/profile");

            return View(userData);
        }
        catch (MicrosoftIdentityWebChallengeUserException ex)
        {
            // Incremental consent required
            // Redirect user to consent page
            return Challenge(
                new AuthenticationProperties
                {
                    RedirectUri = "/Profile"
                },
                OpenIdConnectDefaults.AuthenticationScheme);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "Failed to call downstream API");
            return View("Error");
        }
    }
}

5. Appeler l’API en aval à partir de la page Razor

Injectez IDownstreamApi dans votre modèle Razor Page et appelez l’API.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
public class ProfileModel : PageModel
{
    private readonly IDownstreamApi _downstreamApi;

    public UserData UserData { get; set; }

    public ProfileModel(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task OnGetAsync()
    {
        try
        {
            UserData = await _downstreamApi.GetForUserAsync<UserData>(
                "MyAPI",
                options => options.RelativePath = "api/profile");
        }
        catch (MicrosoftIdentityWebChallengeUserException)
        {
            // Handle incremental consent
            // User will be redirected to consent page
            throw;
        }
    }
}

Appeler Microsoft Graph

Pour les appels Microsoft API Graph, utilisez le GraphServiceClient dédié.

Installer des packages

Installez le package Microsoft Graph pour Microsoft. Identity.Web.

dotnet add package Microsoft.Identity.Web.GraphServiceClient

Configurez le client Graph dans votre code de démarrage.

// Startup configuration
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddMicrosoftGraph(options =>
    {
        options.Scopes = "user.read mail.read";
    })
    .AddInMemoryTokenCaches();

Appelez le API Graph

Injectez GraphServiceClient dans votre contrôleur pour appeler des points de terminaison Microsoft Graph.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph;

[Authorize]
{
    private readonly GraphServiceClient _graphClient;

    public HomeController(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }

    public async Task<IActionResult> Index()
    {
        // Get current user's profile
        var user = await _graphClient.Me.GetAsync();

        // Get user's emails
        var messages = await _graphClient.Me.Messages
            .GetAsync(config => config.QueryParameters.Top = 10);

        return View(new { User = user, Messages = messages });
    }
}

En savoir plus sur l’intégration Microsoft Graph


Appeler des clients Kit de développement logiciel (SDK) Azure

Pour appeler des services Azure, utilisez MicrosoftIdentityTokenCredential.

Installer des packages

Installez les packages Kit de développement logiciel (SDK) Azure requis.

dotnet add package Microsoft.Identity.Web.Azure
dotnet add package Azure.Storage.Blobs

Inscrivez les informations d’identification du jeton Microsoft Entra dans votre code de démarrage.

using Microsoft.Identity.Web;

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

// Add Azure token credential
builder.Services.AddMicrosoftIdentityAzureTokenCredential();

Accéder aux services de Azure

Injectez les informations d’identification du jeton et utilisez-la avec Kit de développement logiciel (SDK) Azure clients.

using Azure.Storage.Blobs;
using Microsoft.Identity.Web;

public class StorageController : Controller
{
    private readonly MicrosoftIdentityTokenCredential _credential;

    public StorageController(MicrosoftIdentityTokenCredential credential)
    {
        _credential = credential;
    }

    [Authorize]
    public async Task<IActionResult> ListBlobs()
    {
        var blobClient = new BlobServiceClient(
            new Uri("https://myaccount.blob.core.windows.net"),
            _credential);

        var container = blobClient.GetBlobContainerClient("mycontainer");
        var blobs = new List<string>();

        await foreach (var blob in container.GetBlobsAsync())
        {
            blobs.Add(blob.Name);
        }

        return View(blobs);
    }
}

En savoir plus sur l’intégration Kit de développement logiciel (SDK) Azure


Appeler des API personnalisées avec IDownstreamApi

Pour vos propres API REST, IDownstreamApi fournit une approche simple basée sur la configuration.

Configurer l’API

Définissez les paramètres d’API en aval dans appsettings.json.

{
  "DownstreamApis": {
    "MyAPI": {
      "BaseUrl": "https://myapi.example.com",
      "Scopes": ["api://my-api-id/access_as_user"],
      "RequestAppToken": false
    }
  }
}

Envoyer des requêtes GET

Récupérez des données de l’API en aval avec des paramètres de requête facultatifs.

// Simple GET
var data = await _downstreamApi.GetForUserAsync<MyData>(
    "MyAPI",
    options => options.RelativePath = "api/resource");

// GET with query parameters
var results = await _downstreamApi.GetForUserAsync<SearchResults>(
    "MyAPI",
    options =>
    {
        options.RelativePath = "api/search";
        options.QueryParameters = new Dictionary<string, string>
        {
            ["query"] = "test",
            ["limit"] = "10"
        };
    });

Envoyer des requêtes POST

Créez une ressource sur l’API en aval en publiant un corps de requête.

var newItem = new CreateItemRequest
{
    Name = "New Item",
    Description = "Item description"
};

var created = await _downstreamApi.PostForUserAsync<CreateItemRequest, CreatedItem>(
    "MyAPI",
    newItem,
    options => options.RelativePath = "api/items");

Envoyer des requêtes PUT et DELETE

Mettez à jour ou supprimez des ressources sur l’API en aval.

// PUT request
var updated = await _downstreamApi.PutForUserAsync<UpdateRequest, UpdatedItem>(
    "MyAPI",
    updateData,
    options => options.RelativePath = "api/items/123");

// DELETE request
await _downstreamApi.DeleteForUserAsync(
    "MyAPI",
    null,
    options => options.RelativePath = "api/items/123");

En savoir plus sur les appels d’API personnalisés


Utiliser IAuthorizationHeaderProvider (avancé)

Pour un contrôle maximal sur les requêtes HTTP, utilisez IAuthorizationHeaderProvider.

Inscrire le client HTTP

Inscrivez un client HTTP nommé pour votre API en aval.

builder.Services.AddHttpClient("MyAPI", client =>
{
    client.BaseAddress = new Uri("https://myapi.example.com");
});

Créer des requêtes HTTP personnalisées

Générez et envoyez des requêtes HTTP avec des en-têtes et une autorisation personnalisés.

using Microsoft.Identity.Abstractions;

public class CustomApiService
{
    private readonly IAuthorizationHeaderProvider _authProvider;
    private readonly IHttpClientFactory _httpClientFactory;

    public CustomApiService(
        IAuthorizationHeaderProvider authProvider,
        IHttpClientFactory httpClientFactory)
    {
        _authProvider = authProvider;
        _httpClientFactory = httpClientFactory;
    }

    public async Task<MyData> GetDataAsync()
    {
        // Get authorization header
        var authHeader = await _authProvider.CreateAuthorizationHeaderForUserAsync(
            new[] { "api://my-api-id/access_as_user" });

        // Create HTTP request with custom logic
        var client = _httpClientFactory.CreateClient("MyAPI");
        var request = new HttpRequestMessage(HttpMethod.Get, "api/resource");
        request.Headers.Add("Authorization", authHeader);
        request.Headers.Add("X-Custom-Header", "custom-value");

        var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync<MyData>();
    }
}

En savoir plus sur la logique HTTP personnalisée


Lorsque vous appelez des API en aval, votre application peut avoir besoin de gérer des scénarios où l’interaction utilisateur est nécessaire. Cela se produit dans trois scénarios principaux :

  1. Consentement incrémentiel - Demande d’autorisations supplémentaires au-delà de ce qui a été accordé initialement
  2. Accès conditionnel - Répondre aux exigences de sécurité telles que l’authentification multifacteur, la conformité des appareils ou les stratégies d’emplacement
  3. Élimination du cache de jeton - Reconstitution du cache de jeton après le redémarrage de l'application ou l'expiration du cache

Microsoft. Identity.Web fournit une gestion automatique de ces scénarios avec un code minimal requis.

Comprendre le flux

Lorsque Microsoft.Identity.Web détecte que l’interaction utilisateur est nécessaire, Microsoft.Identity.Web lève une MicrosoftIdentityWebChallengeUserException. Le cadre gère automatiquement cela via l’attribut [AuthorizeForScopes] ou le service MicrosoftIdentityConsentAndConditionalAccessHandler (pour Blazor), qui :

  1. Redirige l’utilisateur vers Microsoft Entra ID pour le consentement/l’authentification
  2. Conserve l’URL de la requête d’origine
  3. Retourne l’utilisateur à sa destination prévue après avoir terminé le flux
  4. Met en cache les jetons nouvellement acquis

Passer en revue les conditions préalables requises

Pour activer la gestion automatique du consentement, vérifiez que vous Program.cs incluez la configuration suivante.

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("MyAPI", builder.Configuration.GetSection("MyAPI"))
    .AddInMemoryTokenCaches();

// For MVC applications - enables the account controller
builder.Services.AddControllersWithViews()
    .AddMicrosoftIdentityUI();

// Ensure routes are mapped
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers(); // Required for AccountController

Appliquer [AuthorizeForScopes] dans les contrôleurs MVC

L’attribut [AuthorizeForScopes] , défini sur les contrôleurs ou les actions du contrôleur, gère MicrosoftIdentityWebChallengeUserException automatiquement en exigeant l’utilisateur quand des autorisations supplémentaires sont nécessaires.

Déclarer des étendues en ligne

Spécifiez les étendues requises directement dans l’attribut.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public class ProfileController : Controller
{
    private readonly IDownstreamApi _downstreamApi;

    public ProfileController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<IActionResult> Index()
    {
        // AuthorizeForScopes automatically handles consent challenges
        var userData = await _downstreamApi.GetForUserAsync<UserData>(
            "MyAPI",
            options => options.RelativePath = "api/profile");

        return View(userData);
    }

    // Different action requires additional scopes
    [AuthorizeForScopes(Scopes = new[] { "user.read", "mail.read" })]
    public async Task<IActionResult> Emails()
    {
        var emails = await _downstreamApi.GetForUserAsync<EmailList>(
            "GraphAPI",
            options => options.RelativePath = "me/messages");

        return View(emails);
    }
}

Configurer des étendues à partir d’appsettings

Stocker les étendues dans appsettings.json pour une meilleure maintenabilité.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "common",
    "ClientId": "[Your-Client-ID]",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "[Your-Client-Secret]"
      }
    ]
  },
  "DownstreamApis": {
    "TodoList": {
      "BaseUrl": "https://localhost:5001",
      "Scopes": [ "api://[API-Client-ID]/access_as_user" ]
    },
    "GraphAPI": {
      "BaseUrl": "https://graph.microsoft.com/v1.0",
      "Scopes": [ "https://graph.microsoft.com/Mail.Read", "https://graph.microsoft.com/Mail.Send" ]
    }
  }
}

Contrôleur:

[Authorize]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:TodoList:Scopes:0")]
public class TodoListController : Controller
{
    private readonly IDownstreamApi _downstreamApi;

    public TodoListController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<IActionResult> Index()
    {
        var todos = await _downstreamApi.GetForUserAsync<IEnumerable<TodoItem>>(
            "TodoList",
            options => options.RelativePath = "api/todolist");

        return View(todos);
    }

    [AuthorizeForScopes(ScopeKeySection = "DownstreamApis:GraphAPI:Scopes:0")]
    public async Task<IActionResult> EmailTodos()
    {
        // If user hasn't consented to Mail.Send, they'll be prompted
        await _downstreamApi.PostForUserAsync<EmailMessage, object>(
            "GraphAPI",
            new EmailMessage { /* ... */ },
            options => options.RelativePath = "me/sendMail");

        return RedirectToAction("Index");
    }
}

Configurer ID externe Microsoft Entra avec des flux d’utilisateurs

Pour les applications ID externe (B2C) avec plusieurs flux utilisateur, spécifiez le flux utilisateur dans l’attribut.

[Authorize]
public class AccountController : Controller
{
    private const string SignUpSignInFlow = "b2c_1_susi";
    private const string EditProfileFlow = "b2c_1_edit_profile";
    private const string ResetPasswordFlow = "b2c_1_reset";

    [AuthorizeForScopes(
        ScopeKeySection = "DownstreamApis:TodoList:Scopes:0",
        UserFlow = SignUpSignInFlow)]
    public async Task<IActionResult> Index()
    {
        var data = await _downstreamApi.GetForUserAsync<UserData>(
            "TodoList",
            options => options.RelativePath = "api/data");

        return View(data);
    }

    [AuthorizeForScopes(
        Scopes = new[] { "openid", "offline_access" },
        UserFlow = EditProfileFlow)]
    public async Task<IActionResult> EditProfile()
    {
        // This triggers the B2C edit profile flow
        return RedirectToAction("Index");
    }
}

Appliquer [AuthorizeForScopes] dans les pages Razor

Appliquez [AuthorizeForScopes] à la classe de modèle de page :

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Identity.Web;
using Microsoft.Identity.Abstractions;

[Authorize]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:MyAPI:Scopes:0")]
public class IndexModel : PageModel
{
    private readonly IDownstreamApi _downstreamApi;

    public UserData UserData { get; set; }

    public IndexModel(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task OnGetAsync()
    {
        // Automatically handles consent challenges
        UserData = await _downstreamApi.GetForUserAsync<UserData>(
            "MyAPI",
            options => options.RelativePath = "api/profile");
    }
}

Les applications Blazor Server nécessitent une gestion explicite des exceptions à l’aide du MicrosoftIdentityConsentAndConditionalAccessHandler service.

Configurer Program.cs

Inscrivez le gestionnaire de consentement pour Blazor Server dans votre code de démarrage.

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApis("TodoList", builder.Configuration.GetSection("DownstreamApis"))
    .AddInMemoryTokenCaches();

// Register the consent handler for Blazor
builder.Services.AddServerSideBlazor()
    .AddMicrosoftIdentityConsentHandler();

Créer le composant Blazor

Envelopper les appels d'API dans les blocs try-catch et utiliser ConsentHandler.HandleException() pour gérer les défis de consentement.

@page "/todolist"
@using Microsoft.Identity.Web
@using Microsoft.Identity.Abstractions
@using MyApp.Models

@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
@inject IDownstreamApi DownstreamApi

<h3>My Todo List</h3>

@if (todos == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <ul>
        @foreach (var todo in todos)
        {
            <li>@todo.Title</li>
        }
    </ul>
}

@code {
    private IEnumerable<TodoItem> todos;

    protected override async Task OnInitializedAsync()
    {
        await LoadTodosAsync();
    }

    [AuthorizeForScopes(ScopeKeySection = "DownstreamApis:TodoList:Scopes:0")]
    private async Task LoadTodosAsync()
    {
        try
        {
            todos = await DownstreamApi.GetForUserAsync<IEnumerable<TodoItem>>(
                "TodoList",
                options => options.RelativePath = "api/todolist");
        }
        catch (Exception ex)
        {
            // Handles MicrosoftIdentityWebChallengeUserException
            // and initiates user consent/authentication flow
            ConsentHandler.HandleException(ex);
        }
    }

    private async Task AddTodoAsync(string title)
    {
        try
        {
            await DownstreamApi.PostForUserAsync<TodoItem, TodoItem>(
                "TodoList",
                new TodoItem { Title = title },
                options => options.RelativePath = "api/todolist");

            await LoadTodosAsync();
        }
        catch (Exception ex)
        {
            ConsentHandler.HandleException(ex);
        }
    }
}

Gérer manuellement les exceptions (avancées)

Si vous avez besoin d’une logique de flux de consentement personnalisée, gérez MicrosoftIdentityWebChallengeUserException explicitement :

[Authorize]
public class AdvancedController : Controller
{
    private readonly IDownstreamApi _downstreamApi;
    private readonly ILogger<AdvancedController> _logger;

    public AdvancedController(
        IDownstreamApi downstreamApi,
        ILogger<AdvancedController> logger)
    {
        _downstreamApi = downstreamApi;
        _logger = logger;
    }

    public async Task<IActionResult> SendEmail()
    {
        try
        {
            await _downstreamApi.PostForUserAsync<EmailMessage, object>(
                "GraphAPI",
                new EmailMessage
                {
                    Subject = "Test",
                    Body = "Test message"
                },
                options => options.RelativePath = "me/sendMail");

            return RedirectToAction("Success");
        }
        catch (MicrosoftIdentityWebChallengeUserException ex)
        {
            // Log the consent requirement
            _logger.LogWarning(
                "Consent required for scopes: {Scopes}. Challenging user.",
                string.Join(", ", ex.Scopes));

            // Custom properties for redirect
            var properties = new AuthenticationProperties
            {
                RedirectUri = Url.Action("SendEmail", "Advanced"),
            };

            // Add custom state if needed
            properties.Items["consent_attempt"] = "1";

            return Challenge(properties, OpenIdConnectDefaults.AuthenticationScheme);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "Failed to send email");
            return View("Error");
        }
    }
}

Gérer les scénarios d’accès conditionnel

Les stratégies d’accès conditionnel peuvent nécessiter des facteurs d’authentification supplémentaires. Le processus est identique au consentement progressif :

[Authorize]
[AuthorizeForScopes(ScopeKeySection = "DownstreamApis:SecureAPI:Scopes:0")]
public class SecureDataController : Controller
{
    private readonly IDownstreamApi _downstreamApi;

    public SecureDataController(IDownstreamApi downstreamApi)
    {
        _downstreamApi = downstreamApi;
    }

    public async Task<IActionResult> Index()
    {
        // If conditional access requires MFA, AuthorizeForScopes
        // automatically challenges the user
        var sensitiveData = await _downstreamApi.GetForUserAsync<SensitiveData>(
            "SecureAPI",
            options => options.RelativePath = "api/sensitive");

        return View(sensitiveData);
    }
}

Déclencheurs d’accès conditionnel courants :

  • Authentification multifacteur (MFA)
  • Exigences de conformité pour l'appareil
  • Emplacement réseau approuvé
  • Acceptation des conditions d’utilisation
  • Condition requise pour la modification du mot de passe

Suivre les bonnes pratiques

Appliquez ces recommandations lors de l’implémentation du consentement et de la gestion de l’accès conditionnel.

Utiliser [AuthorizeForScopes] - Approche la plus simple pour les contrôleurs MVC et les pages Razor

Stocker des étendues dans la configuration - Utilisez ScopeKeySection = "DownstreamApis:ApiName:Scopes:0" pour référencer les étendues dans appsettings.json

Appliquer au niveau du contrôleur - Définir les périmètres par défaut sur le contrôleur, redéfinir pour des actions spécifiques

Gérer les exceptions dans Blazor - Toujours encapsuler les appels d’API avec try-catch et utiliser ConsentHandler.HandleException()

Permettez la relance des exceptions - Si vous interceptez , MicrosoftIdentityWebChallengeUserException afin que [AuthorizeForScopes] puisse la traiter

Tester l’accès conditionnel : vérifiez que votre application gère correctement l’authentification multifacteur et d’autres stratégies d’autorité de certification

Ne supprimez pas les exceptions - Intercepter sans lever de nouveau perturbe le flux de consentement

Ne pas mettre en cache les réponses indéfiniment - Les jetons expirent ; conception pour la ré-authentification


Autorisations statiques (consentement administrateur)

Toutes les autorisations sont demandées lors de l’inscription de l’application et accordées par un administrateur client :

Avantages :

  • Les utilisateurs ne voient jamais les invites de consentement
  • Obligatoire pour les applications Microsoft internes
  • Expérience utilisateur plus simple

Inconvénients :

  • Nécessite l’implication de l’administrateur du locataire
  • Sur-privilégié depuis le début
  • Moins flexible pour les scénarios multilocataires

Configuration:

// Request all pre-approved scopes for Microsoft Graph
var scopes = new[] { "https://graph.microsoft.com/.default" };

var userData = await _downstreamApi.GetForUserAsync<UserData>(
    "GraphAPI",
    options =>
    {
        options.RelativePath = "me";
        options.Scopes = scopes; // Use .default scope
    });

Consentement incrémentiel (dynamique)

Les autorisations sont demandées selon les besoins pendant l’exécution :

Avantages :

  • Meilleure sécurité (principe du privilège minimum)
  • Les utilisateurs consentent à ce qu’ils utilisent réellement
  • Fonctionne pour les applications multi-locataires

Inconvénients :

  • Les utilisateurs pourraient être interrompus par des invites de consentement
  • Nécessite le traitement MicrosoftIdentityWebChallengeUserException

Recommandation: Utiliser le consentement incrémentiel pour les applications mutualisées ; utiliser des autorisations statiques pour les applications d’entreprise internes où le consentement administrateur est garanti


Configurer la mise en cache des jetons

Microsoft. Identity.Web met en cache les jetons pour améliorer les performances et réduire les appels à Microsoft Entra.

Utiliser le cache en mémoire (par défaut)

Ajoutez un cache de jetons en mémoire pour les scénarios de développement ou de serveur unique.

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches(); // In-memory cache

Utiliser pour :

  • Development
  • Déploiements à serveur unique
  • Petite base d’utilisateurs

Limitations :

  • Non partagé entre les instances
  • Perdu lors du redémarrage de l’application
  • La consommation de mémoire augmente avec les utilisateurs

Configurez un cache distribué tel que Redis ou SQL Server pour les déploiements de production.

// Install: Microsoft.Identity.Web.TokenCache

// Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration["Redis:ConnectionString"];
    options.InstanceName = "MyApp_";
});

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

// SQL Server
builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = builder.Configuration["SqlCache:ConnectionString"];
    options.SchemaName = "dbo";
    options.TableName = "TokenCache";
});

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

Utiliser pour :

  • Déploiements à plusieurs serveurs (à charge équilibrée)
  • Scénarios de haute disponibilité
  • Base d’utilisateurs volumineuse
  • Cache persistant entre les redémarrages

Gérer les échecs d’acquisition de jetons

Intercepter les exceptions courantes

Le code suivant montre comment intercepter et gérer les exceptions d’acquisition de jetons les plus courantes.

try
{
    var data = await _downstreamApi.GetForUserAsync<MyData>(
        "MyAPI",
        options => options.RelativePath = "api/resource");
}
catch (MicrosoftIdentityWebChallengeUserException ex)
{
    // User needs to consent or reauthenticate
    _logger.LogWarning($"User consent required: {ex.Message}");
    return Challenge(new AuthenticationProperties { RedirectUri = Request.Path });
}
catch (MsalUiRequiredException ex)
{
    // User interaction required (sign-in again, MFA, etc.)
    _logger.LogWarning($"User interaction required: {ex.Message}");
    return Challenge(OpenIdConnectDefaults.AuthenticationScheme);
}
catch (MsalServiceException ex)
{
    // Service error (Microsoft Entra ID unavailable, etc.)
    _logger.LogError(ex, "Microsoft Entra ID service error");
    return StatusCode(503, "Authentication service temporarily unavailable");
}
catch (HttpRequestException ex)
{
    // Downstream API unreachable
    _logger.LogError(ex, "Downstream API call failed");
    return StatusCode(503, "Downstream service unavailable");
}

Implémenter une dégradation en douceur

Chargez des données facultatives à partir d’API en aval et revenez aux valeurs par défaut lorsque les appels échouent.

public async Task<IActionResult> Dashboard()
{
    var model = new DashboardModel();

    // Try to load optional data from downstream API
    try
    {
        model.EnrichedData = await _downstreamApi.GetForUserAsync<EnrichedData>(
            "MyAPI",
            options => options.RelativePath = "api/enriched");
    }
    catch (Exception ex)
    {
        _logger.LogWarning(ex, "Failed to load enriched data, using defaults");
        model.EnrichedData = new EnrichedData { /* defaults */ };
    }

    return View(model);
}

Implémenter OWIN (infrastructure .NET)

Pour les applications web basées sur OWIN sur .NET Framework, procédez comme suit.

1. Installer des packages

Installez les packages NuGet requis.

Install-Package Microsoft.Identity.Web.OWIN
Install-Package Microsoft.Owin.Host.SystemWeb

2. Configurer le démarrage

Configurez l’authentification et l’acquisition de jetons pour Microsoft Entra dans la classe de démarrage OWIN.

using Microsoft.Identity.Web;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Owin;

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        app.AddMicrosoftIdentityWebApp(
            Configuration,
            configSectionName: "AzureAd",
            openIdConnectScheme: "OpenIdConnect",
            cookieScheme: CookieAuthenticationDefaults.AuthenticationType,
            subscribeToOpenIdConnectMiddlewareDiagnosticsEvents: true);

        app.EnableTokenAcquisitionToCallDownstreamApi();
        app.AddDistributedTokenCaches();
    }
}

3. Appeler l’API en aval

Acquérir un jeton et appeler l’API en aval à partir d’un contrôleur MVC.

using Microsoft.Identity.Web;
using System.Threading.Tasks;
using System.Web.Mvc;

[Authorize]
public class ProfileController : Controller
{
    public async Task<ActionResult> Index()
    {
        var downstreamApi = TokenAcquirerFactory.GetDefaultInstance()
            .GetTokenAcquirer()
            .GetDownstreamApi();

        var userData = await downstreamApi.GetForUserAsync<UserData>(
            "MyAPI",
            options => options.RelativePath = "api/profile");

        return View(userData);
    }
}

Note : la prise en charge d'OWIN présente des différences par rapport à ASP.NET Core. Pour plus d’informations, consultez la documentation OWIN .


Suivre les meilleures pratiques de sécurité

Gérer les périmètres

Appliquez le principe du privilège minimum lors de la demande d’autorisations d’API.

À faire :

  • Demander uniquement des étendues dont vous avez besoin
  • Utiliser le consentement incrémentiel pour les fonctionnalités avancées
  • Documenter les étendues requises dans votre application

À ne pas faire :

  • Demander des étendues inutiles en amont
  • Demander des autorisations réservées aux administrateurs sans justification
  • Supposons que toutes les étendues seront accordées

Gérer les jetons de manière sécurisée

Suivez ces instructions pour protéger les jetons d’accès dans votre application.

À faire :

  • Laissez Microsoft. Identity.Web manage tokens
  • Utiliser le cache distribué en production
  • Gérer les échecs d’acquisition de jetons avec élégance

À ne pas faire :

  • Stockez les jetons vous-même
  • Jetons d’accès au journal
  • Envoyer des jetons au code côté client

Gérer les erreurs

Implémentez une gestion robuste des erreurs pour les échecs d’appel d’API et d’authentification.

À faire :

  • Intercepter et gérer les exceptions de consentement
  • Fournir des messages d’erreur clairs aux utilisateurs
  • Erreurs de journalisation pour le débogage

À ne pas faire :

  • Exposer des erreurs de jeton aux utilisateurs
  • Échec silencieux des appels d’API
  • Ignorer les exceptions d’authentification

Résoudre les problèmes courants

Passez en revue ces solutions pour connaître les erreurs d’authentification fréquemment rencontrées.

Problème : « AADSTS65001 : l’utilisateur ou l’administrateur n’a pas consenti »

Cause : L’utilisateur n’a pas consenti aux étendues requises.

Solution:

catch (MicrosoftIdentityWebChallengeUserException ex)
{
    // Redirect to consent page
    return Challenge(
        new AuthenticationProperties { RedirectUri = Request.Path },
        OpenIdConnectDefaults.AuthenticationScheme);
}

Problème : « AADSTS50076 : authentification multifacteur requise »

Cause : L’utilisateur doit effectuer l’authentification multifacteur (MFA).

Solution:

catch (MsalUiRequiredException)
{
    // Redirect user to sign in with MFA
    return Challenge(OpenIdConnectDefaults.AuthenticationScheme);
}

Problème : Les jetons ne sont pas persistants entre les redémarrages de l’application

Cause : Utilisation du cache en mémoire.

Solution : Basculer vers le cache distribué (Redis, SQL Server ou Cosmos DB).

Problème : 401 Non autorisé à partir de l’API en aval

Causes possibles :

  • Demandes d'étendues incorrectes
  • Autorisation d’API non accordée dans l’inscription d’application
  • Le jeton a expiré

Solution:

  1. Vérifiez que les étendues dans appsettings.json correspondent aux exigences de l'API.
  2. Vérifier que l’inscription de l’application dispose d’autorisations d’API
  3. Vérifier que les jetons sont mis en cache et actualisés

Pour obtenir des diagnostics détaillés : Consultez le Guide de journalisation et de diagnostic pour les ID de corrélation, le débogage du cache de jetons et les modèles de résolution des problèmes complets.


Optimiser les performances

Planifier la stratégie de mise en cache des jetons

Sélectionnez une stratégie de mise en cache qui correspond à votre topologie de déploiement.

  • Utiliser le cache distribué pour les déploiements multiserveurs
  • Configurer l’expiration du cache appropriée
  • Surveiller les performances du cache

Réduire les demandes de jetons

Microsoft. Identity.Web met automatiquement en cache les jetons. Les deux appels dans l’exemple suivant réutilisent le même jeton mis en cache.

// Bad: Multiple token acquisitions
var profile = await _downstreamApi.GetForUserAsync<Profile>(
    "API",
    options => options.RelativePath = "profile");
var settings = await _downstreamApi.GetForUserAsync<Settings>(
    "API",
    options => options.RelativePath = "settings");

// Good: Single token, multiple calls (token is cached)
// Both calls use the same cached token
var profile = await _downstreamApi.GetForUserAsync<Profile>(
    "API",
    options => options.RelativePath = "profile");
var settings = await _downstreamApi.GetForUserAsync<Settings>(
    "API",
    options => options.RelativePath = "settings");

Effectuer des appels d’API parallèles

Appelez simultanément plusieurs API en aval pour réduire la latence globale.

// Call multiple APIs in parallel
var profileTask = _downstreamApi.GetForUserAsync<Profile>(
    "API1",
    options => options.RelativePath = "profile");
var settingsTask = _downstreamApi.GetForUserAsync<Settings>(
    "API2",
    options => options.RelativePath = "settings");

await Task.WhenAll(profileTask, settingsTask);

var profile = profileTask.Result;
var settings = settingsTask.Result;

Trouvez des conseils supplémentaires pour les scénarios connexes.