Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
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.
Informazioni generali
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.
Flusso del token 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
Prerequisiti
- 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)
implementazione ASP.NET Core
1. Configurare l'autenticazione e l'acquisizione di token
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
{
"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
Opzione A: Registrare API denominate
using Microsoft.Identity.Web;
// Register multiple downstream APIs
builder.Services.AddDownstreamApis(
builder.Configuration.GetSection("DownstreamApis"));
Option B: Usare Microsoft Graph Helper
// Install: Microsoft.Identity.Web.GraphServiceClient
builder.Services.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApis:GraphAPI"));
4. Chiamare l'API downstream dal controller
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. Chiamare l'API downstream dalla pagina Razor
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;
}
}
}
Uso di Microsoft Graph
Per le chiamate a Microsoft API Graph, usare il GraphServiceClient dedicato.
Setup
dotnet add package Microsoft.Identity.Web.GraphServiceClient
// Startup configuration
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(options =>
{
options.Scopes = "user.read mail.read";
})
.AddInMemoryTokenCaches();
Utilizzo
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph;
[Authorize]
public class HomeController : Controller
{
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
Uso di client Azure SDK
Per chiamare i servizi di Azure, usare MicrosoftIdentityTokenCredential:
Setup
dotnet add package Microsoft.Identity.Web.Azure
dotnet add package Azure.Storage.Blobs
using Microsoft.Identity.Web;
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
// Add Azure token credential
builder.Services.AddMicrosoftIdentityAzureTokenCredential();
Utilizzo
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
Uso di API personalizzate con IDownstreamApi
Per le PROPRIE API REST, IDownstreamApi offre un approccio semplice basato sulla configurazione:
Configurazione
{
"DownstreamApis": {
"MyAPI": {
"BaseUrl": "https://myapi.example.com",
"Scopes": ["api://my-api-id/access_as_user"],
"RequestAppToken": false
}
}
}
Utilizzo - Richiesta GET
// 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"
};
});
Utilizzo - Richiesta POST
var newItem = new CreateItemRequest
{
Name = "New Item",
Description = "Item description"
};
var created = await _downstreamApi.PostForUserAsync<CreateItemRequest, CreatedItem>(
"MyAPI",
newItem,
options => options.RelativePath = "api/items");
Utilizzo - PUT e DELETE
// 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
Uso di IAuthorizationHeaderProvider (avanzato)
Per il controllo massimo sulle richieste HTTP, usare IAuthorizationHeaderProvider:
Setup
builder.Services.AddHttpClient("MyAPI", client =>
{
client.BaseAddress = new Uri("https://myapi.example.com");
});
Utilizzo
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
Consenso incrementale e accesso condizionale
Quando si chiamano API downstream, l'applicazione potrebbe dover gestire scenari in cui è necessaria l'interazione dell'utente. Questo avviene in tre scenari principali:
- Consenso incrementale : richiesta di autorizzazioni aggiuntive oltre a quanto inizialmente concesso
- Accesso condizionale - Soddisfare i requisiti di sicurezza, ad esempio MFA, conformità dei dispositivi o criteri di posizione
- 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.
Informazioni sul 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:
- Reindirizza l'utente a Microsoft Entra ID per il consenso o l'autenticazione
- Mantiene l'URL della richiesta originale
- Restituisce l'utente alla destinazione desiderata dopo aver completato il flusso
- Memorizza nella cache i token appena acquisiti
Prerequisiti
Per abilitare la gestione automatica del consenso, assicurarsi che Program.cs includa:
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
Controller MVC : uso di [AuthorizeForScopes]
L'attributo [AuthorizeForScopes] , impostato su controller o azioni del controller, gestisce MicrosoftIdentityWebChallengeUserException automaticamente sfidando l'utente quando sono necessarie autorizzazioni aggiuntive.
Ambiti dichiarativi
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);
}
}
ambiti basati su configurazione
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");
}
}
Azure AD B2C con i flussi degli utenti
Per le applicazioni B2C con più flussi utente:
[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");
}
}
Pagine Razor - utilizzo di [AuthorizeForScopes]
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");
}
}
Blazor Server : uso di MicrosoftIdentityConsentAndConditionalAccessHandler
Le applicazioni Blazor Server richiedono la gestione esplicita delle eccezioni usando il MicrosoftIdentityConsentAndConditionalAccessHandler servizio.
configurazione di Program.cs
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddDownstreamApis("TodoList", builder.Configuration.GetSection("DownstreamApis"))
.AddInMemoryTokenCaches();
// Register the consent handler for Blazor
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
Componente Blazor
@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);
}
}
}
Gestione manuale delle eccezioni (avanzata)
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");
}
}
}
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
Procedure consigliate
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 e consenso incrementale
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
Memorizzazione nella cache dei token
Microsoft. Identity.Web memorizza nella cache i token per migliorare le prestazioni e ridurre le chiamate a Microsoft Entra ID.
cache In-Memory (impostazione predefinita)
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
Cache distribuita (consigliata per la 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
Gestione degli errori di acquisizione dei token
Eccezioni 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");
}
Degradazione controllata
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);
}
Implementazione di OWIN (.NET Framework)
Per le applicazioni Web basate su OWIN in .NET Framework:
1. Installare i pacchetti
Install-Package Microsoft.Identity.Web.OWIN
Install-Package Microsoft.Owin.Host.SystemWeb
2. Configurare l'avvio
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
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 .
Procedure consigliate per la sicurezza
Gestione dell'ambito
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
Gestione dei token
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
Gestione degli errori
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
Risoluzione dei problemi
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:
- Verificare che gli scopi in appsettings.json soddisfino i requisiti dell'API.
- Verificare che la registrazione dell'app disponga delle autorizzazioni API
- 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.
Considerazioni sulle prestazioni
Strategia di memorizzazione nella cache dei token
- 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
// 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");
Chiamate API parallele
// 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;