Chiamare le API downstream dalle applicazioni web

Questa guida illustra come chiamare le API downstream da applicazioni Web ASP.NET Core e OWIN usando Microsoft. Identity.Web. Nelle app Web si acquisiscono i token per conto dell'utente connesso per chiamare le API con autorizzazioni delegate.

Comprendere il flusso del token

Quando un utente accede all'applicazione Web, è possibile chiamare api downstream (Microsoft Graph, servizi Azure o API personalizzate) per loro conto. Microsoft. Identity.Web gestisce l'acquisizione dei token, la memorizzazione nella cache e l'aggiornamento automatico.

Esaminare il flusso del token di autenticazione utente

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

Verificare i prerequisiti

Verificare che l'ambiente soddisfi i requisiti seguenti prima di iniziare.

  • App Web configurata con l'autenticazione OpenID Connect
  • Accesso utente funzionante
  • Registrazione dell'app con autorizzazioni API configurate
  • Consenso utente ottenuto (o consenso amministratore concesso)

Implementare ASP.NET Core

1. Configurare l'autenticazione e l'acquisizione di token

Aggiungere i servizi di autenticazione e abilitare l'acquisizione di token nel Program.cs file.

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. Configurare appsettings.json

Definisci la registrazione dell'app per Microsoft Entra ID e le impostazioni dell'API downstream in 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"]
    }
  }
}

Importante: Per le app Web che chiamano LE API downstream, sono necessarie credenziali client (certificato o segreto) oltre alla configurazione di accesso.

3. Aggiungere il supporto dell'API downstream

Scegliere una delle opzioni seguenti per registrare le API downstream.

Opzione A: Registrare API denominate

Il codice seguente registra diverse API a valle dalla configurazione.

using Microsoft.Identity.Web;

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

Option B: Usare Microsoft Graph Helper

Il codice seguente registra il client SDK Microsoft Graph dalla configurazione.

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

4. Chiamare l'API downstream dal controller

Iniettare IDownstreamApi nel tuo controller e chiamare l'API per conto dell'utente connesso.

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. Effettuare una chiamata API downstream dalla pagina Razor

IDownstreamApi Inserire nel modello Razor Page e chiamare 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;
        }
    }
}

Chiamare Microsoft Graph

Per le chiamate a Microsoft API Graph, usare il GraphServiceClient dedicato.

Installare i pacchetti

Installare il pacchetto Microsoft Graph per Microsoft. Identity.Web.

dotnet add package Microsoft.Identity.Web.GraphServiceClient

Configurare il client Graph nel codice di avvio.

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

Chiamare il API Graph

Inserire GraphServiceClient nel controller per chiamare gli endpoint 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 });
    }
}

Altre informazioni sull'integrazione Microsoft Graph


Chiamare i client dell'SDK di Azure

Per chiamare i servizi di Azure, usare MicrosoftIdentityTokenCredential.

Installare i pacchetti

Installare i pacchetti di Azure SDK necessari.

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

Registrare le credenziali del token Microsoft Entra nel codice di avvio.

using Microsoft.Identity.Web;

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

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

Accedere ai servizi di Azure

Immettere le credenziali del token e usarle con i client Azure SDK.

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

Altre informazioni sull'integrazione Azure SDK


Chiamare API personalizzate con IDownstreamApi

Per le API REST personalizzate, IDownstreamApi offre un approccio semplice basato sulla configurazione.

Configurare l'API

Definire le impostazioni dell'API downstream in appsettings.json.

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

Inviare richieste GET

Recuperare dati dall'API downstream con parametri di query facoltativi.

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

Inviare richieste POST

Creare una nuova risorsa nell'API downstream pubblicando un corpo della richiesta.

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

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

Inviare richieste PUT e DELETE

Aggiornare o eliminare risorse nell'API downstream.

// 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");

Altre informazioni sulle chiamate API personalizzate


Usare IAuthorizationHeaderProvider (avanzato)

Per il controllo massimo sulle richieste HTTP, usare IAuthorizationHeaderProvider.

Registrare il client HTTP

Registra un client HTTP nominato per la tua API downstream.

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

Creare richieste HTTP personalizzate

Compilare e inviare richieste HTTP con intestazioni e autorizzazioni personalizzate.

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

Altre informazioni sulla logica HTTP personalizzata


Quando si chiamano API downstream, l'applicazione potrebbe dover gestire scenari in cui è necessaria l'interazione dell'utente. Questo avviene in tre scenari principali:

  1. Consenso incrementale : richiesta di autorizzazioni aggiuntive oltre a quanto inizialmente concesso
  2. Accesso condizionale - Soddisfare i requisiti di sicurezza, ad esempio MFA, conformità dei dispositivi o criteri di posizione
  3. Rimozione della cache dei token : ripopolamento della cache dei token dopo il riavvio o la scadenza della cache dell'applicazione

Microsoft. Identity.Web offre la gestione automatica di questi scenari con codice minimo necessario.

Comprendere il flusso

Quando Microsoft. Identity.Web rileva che l'interazione dell'utente è necessaria, genera un MicrosoftIdentityWebChallengeUserException. Il framework gestisce automaticamente questa operazione tramite l'attributo [AuthorizeForScopes] o il MicrosoftIdentityConsentAndConditionalAccessHandler servizio (per Blazor), che:

  1. Reindirizza l'utente a Microsoft Entra ID per il consenso o l'autenticazione
  2. Mantiene l'URL della richiesta originale
  3. Restituisce l'utente alla destinazione desiderata dopo aver completato il flusso
  4. Memorizza nella cache i token appena acquisiti

Verificare i prerequisiti

Per abilitare la gestione automatica del consenso, assicurati che Program.cs includa la seguente configurazione.

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

Applicare [AuthorizeForScopes] nei controller MVC

L'attributo [AuthorizeForScopes] , impostato su controller o azioni del controller, gestisce MicrosoftIdentityWebChallengeUserException automaticamente sfidando l'utente quando sono necessarie autorizzazioni aggiuntive.

Dichiarare gli ambiti inline

Specificare gli ambiti necessari direttamente nell'attributo .

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

Configurare gli scopi negli 'appsettings'

Archiviare gli ambiti in appsettings.json per una migliore manutenibilità:

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

Controller:

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

Configurare Microsoft Entra per ID esterno con i flussi utente

Per le applicazioni con ID esterno (B2C) con più flussi utente, specificare il flusso utente nell'attributo .

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

Applicare [AuthorizeForScopes] nelle Razor Pages

Applica [AuthorizeForScopes] alla classe del modello di pagina:

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

Le applicazioni Blazor Server richiedono la gestione esplicita delle eccezioni usando il MicrosoftIdentityConsentAndConditionalAccessHandler servizio.

Configurare Program.cs

Registrare il gestore di consenso per Blazor Server nel codice di avvio.

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

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

Creare il componente Blazor

Eseguire il wrapping delle chiamate API in blocchi try-catch e usare ConsentHandler.HandleException() per gestire le problematiche di consenso.

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

Gestire manualmente le eccezioni (avanzate)

Se è necessaria una logica del flusso di consenso personalizzata, gestire MicrosoftIdentityWebChallengeUserException in modo esplicito:

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

Gestire gli scenari di accesso condizionale

I criteri di accesso condizionale possono richiedere fattori di autenticazione aggiuntivi. La gestione è identica al consenso incrementale:

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

Trigger di accesso condizionale comuni:

  • Multi-Factor Authentication (MFA)
  • Requisito del dispositivo conforme
  • Posizione di rete attendibile
  • Accettazione delle condizioni per l'utilizzo
  • Requisito di modifica della password

Seguire le migliori pratiche

Applicare queste raccomandazioni quando si implementano il consenso e la gestione dell'accesso condizionale.

Utilizzare [AuthorizeForScopes] - Approccio più semplice per controller MVC e Razor Pages

Archiviare gli ambiti nella configurazione : usare ScopeKeySection = "DownstreamApis:ApiName:Scopes:0" per fare riferimento agli ambiti in appsettings.json

Applica a livello di controller : impostare gli ambiti predefiniti nel controller, eseguire l'override su azioni specifiche

Gestire le eccezioni in Blazor - Eseguire sempre il wrapping delle chiamate API con try-catch e usare ConsentHandler.HandleException()

Consentire di generare nuovamente le eccezioni : se si intercetta MicrosoftIdentityWebChallengeUserException, generarla nuovamente in modo [AuthorizeForScopes] da poterla elaborare

Testare l'accesso condizionale : verificare che l'app gestisca correttamente mfa e altri criteri della CA

Non sopprimere le eccezioni - Intercettare senza rilanciare rompe il flusso di consenso

Non memorizzare nella cache le risposte per un periodo illimitato : i token scadono; progettare per la ripetizione dell'autenticazione


Autorizzazioni statiche (consenso amministratore)

Tutte le autorizzazioni vengono richieste durante la registrazione dell'app e concesse da un amministratore tenant:

Vantaggi:

  • Gli utenti non visualizzano mai richieste di consenso
  • Obbligatorio per le app Microsoft proprietarie
  • Esperienza utente più semplice

Svantaggi:

  • Richiede il coinvolgimento dell'amministratore tenant
  • Privilegi elevati fin dall'inizio
  • Meno flessibile per scenari multi-tenant

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

Consenso incrementale (dinamico)

Le autorizzazioni vengono richieste in base alle esigenze durante il runtime:

Vantaggi:

  • Maggiore sicurezza (principio dei privilegi minimi)
  • Gli utenti acconsentono a ciò che usano effettivamente
  • Funziona per le app multi-tenant

Svantaggi:

  • Gli utenti possono essere interrotti con richieste di consenso
  • Richiede la gestione MicrosoftIdentityWebChallengeUserException

Raccomandazione: Usare il consenso incrementale per le applicazioni multi-tenant; usare le autorizzazioni statiche per le app aziendali proprietarie in cui è garantito il consenso dell'amministratore


Configurare la memorizzazione nella cache dei token

Microsoft. Identity.Web memorizza nella cache i token per migliorare le prestazioni e ridurre le chiamate a Microsoft Entra.

Usare la cache in memoria (impostazione predefinita)

Aggiungere una cache dei token in memoria per scenari di sviluppo o server singolo.

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

Usare per:

  • Sviluppo
  • Distribuzioni a server singolo
  • Base utenti di piccole dimensioni

Limitations:

  • Non condiviso tra istanze
  • Perso al riavvio dell'app
  • L'utilizzo della memoria aumenta con gli utenti

Configurare una cache distribuita, ad esempio Redis o SQL Server per le distribuzioni di produzione.

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

Usare per:

  • Distribuzioni multiserver (bilanciamento del carico)
  • Scenari di disponibilità elevata
  • Base utenti di grandi dimensioni
  • Cache persistente tra riavvii

Gestire gli errori di acquisizione dei token

Rilevare le eccezioni comuni

Il codice seguente illustra come intercettare e gestire le eccezioni di acquisizione di token più comuni.

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

Implementare una degradazione graduale

Caricare dati facoltativi dalle API downstream ed eseguire il fallback alle impostazioni predefinite quando le chiamate hanno esito negativo.

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

Implementare OWIN (framework di .NET)

Per le applicazioni Web basate su OWIN in .NET Framework, seguire questa procedura.

1. Installare i pacchetti

Installare i pacchetti NuGet necessari.

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

2. Configurare l'avvio

Configurare l'autenticazione e l'acquisizione di token di Microsoft Entra nella classe di avvio 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. Chiamare l'API downstream

Acquisire un token e chiamare l'API a valle da un controller 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: supporto OWIN presenta alcune differenze rispetto a ASP.NET Core. Per informazioni dettagliate, vedere la documentazione di OWIN .


Seguire le procedure consigliate per la sicurezza

Gestire gli ambiti

Applicare il principio dei privilegi minimi quando si richiedono autorizzazioni API.

Do:

  • Richiedere solo gli ambiti di autorizzazione necessari
  • Usare il consenso incrementale per le funzionalità avanzate
  • Documentare gli ambiti necessari nell'app

Don't:

  • Richiedere autorizzazioni non necessarie anticipatamente
  • Richiedere autorizzazioni solo amministratore senza fornire una giustificazione
  • Si supponga che tutti gli ambiti vengano concessi

Gestire i token in modo sicuro

Seguire queste linee guida per proteggere i token di accesso nell'applicazione.

Do:

  • Lasciare Microsoft. Identity.Web gestisce i token
  • Usare la cache distribuita nell'ambiente di produzione
  • Gestire gli errori di acquisizione dei token normalmente

Don't:

  • Archiviare i token manualmente
  • Token di accesso ai log
  • Inviare token al codice lato client

Gestire gli errori

Implementare una gestione affidabile degli errori per l'autenticazione e gli errori delle chiamate API.

Do:

  • Rilevare e gestire le eccezioni di consenso
  • Fornire messaggi di errore chiari agli utenti
  • Errori di log per il debug

Don't:

  • Esporre gli errori del token agli utenti
  • Consenti alle chiamate API di fallire senza notifiche visibili per l'utente
  • Ignorare le eccezioni di autenticazione

Risolvere i problemi comuni

Esaminare queste soluzioni per individuare gli errori di autenticazione rilevati di frequente.

Problema: "AADSTS65001: l'utente o l'amministratore non ha acconsentito"

Causa: L'utente non ha acconsentito agli ambiti obbligatori.

Soluzione:

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

Problema: "AADSTS50076: Autenticazione a più fattori richiesta"

Causa: L'utente deve completare l'autenticazione a più fattori (MFA).

Soluzione:

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

Problema: i token non vengono mantenuti tra i riavvii dell'app

Causa: Uso della cache in memoria.

Solution: Passare alla cache distribuita (Redis, SQL Server o Cosmos DB).

Problema: 401 Non autorizzato dall'API downstream

Possibili cause:

  • Ambiti non corretti richiesti
  • Autorizzazione API non concessa nella registrazione dell'app
  • Token scaduto

Soluzione:

  1. Verificare che gli scopi in appsettings.json soddisfino i requisiti dell'API.
  2. Verificare che la registrazione dell'app disponga delle autorizzazioni API
  3. Verificare che i token vengano memorizzati nella cache e aggiornati

Per una diagnostica dettagliata: Vedere La Guida alla registrazione e alla diagnostica per gli ID di correlazione, il debug della cache dei token e i modelli di risoluzione dei problemi completi.


Ottimizzare le prestazioni

Pianificare la strategia di memorizzazione nella cache dei token

Selezionare una strategia di memorizzazione nella cache corrispondente alla topologia di distribuzione.

  • Usare la cache distribuita per le distribuzioni multiserver
  • Configurare la scadenza della cache appropriata
  • Monitorare le prestazioni della cache

Ridurre al minimo le richieste di token

Microsoft. Identity.Web memorizza automaticamente i token nella cache. Entrambe le chiamate nell'esempio seguente riutilizzano lo stesso token memorizzato nella 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");

Effettuare chiamate API parallele

Chiamare più API downstream contemporaneamente per ridurre la latenza complessiva.

// 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;

Altre indicazioni per gli scenari correlati.