In questa guida pratica si eseguirà la migrazione di un'applicazione client riservata da Azure Active Directory Authentication Library per .NET (ADAL.NET) a Libreria di Autenticazione Microsoft per .NET (MSAL.NET). Le applicazioni client riservate includono app Web, API Web e applicazioni daemon che chiamano un altro servizio per proprio conto. Per altre informazioni sulle app riservate, vedere Flussi di autenticazione e scenari di applicazione. Se l'app è basata su ASP.NET Core, vedi Microsoft. Identity.Web.
Per le registrazioni dell'app:
- Non è necessario creare una nuova registrazione dell'app. (Mantieni lo stesso ID cliente.)
- Non è necessario modificare le preautenzioni (autorizzazioni API con consenso amministratore).
Passaggi per la migrazione
Trovare il codice che usa ADAL.NET nell'app.
Il codice che usa ADAL in un'app client riservata crea un'istanza AuthenticationContext e chiama AcquireTokenByAuthorizationCode o una sostituzione di AcquireTokenAsync con i parametri seguenti:
- Una stringa
resourceId. Questa variabile è l'URI ID app dell'API Web che si vuole chiamare.
- Istanza di
IClientAssertionCertificate o ClientAssertion. Questa istanza fornisce le credenziali client per l'app per dimostrare l'identità dell'app.
Dopo aver identificato che sono presenti app che usano ADAL.NET, installare il pacchetto NuGet MSAL.NET Microsoft. Identity.Client e aggiornare i riferimenti alla libreria di progetti. Per altre informazioni, vedere Installare un pacchetto NuGet. Per usare i serializzatori di cache dei token, installare Microsoft. Identity.Web.TokenCache.
Aggiornare il codice in base allo scenario client riservato. Alcuni passaggi sono comuni e si applicano a tutti gli scenari client riservati. Altri passaggi sono univoci per ogni scenario.
Scenari client riservati:
È possibile che sia stato fornito un wrapper intorno ad ADAL.NET per gestire i certificati e la memorizzazione nella cache. Questa guida usa lo stesso approccio per illustrare il processo di migrazione da ADAL.NET a MSAL.NET. Tuttavia, questo codice è solo a scopo dimostrativo. Non copiare/incollare questi wrapper o integrarli nel codice così com'è.
Eseguire la migrazione di app daemon
Gli scenari daemon usano il flusso di credenziali client OAuth2.0. Sono anche dette chiamate tra servizi. L'app acquisisce un token per proprio conto, non per conto di un utente.
Scoprire se il codice usa scenari daemon
Il codice ADAL per l'app usa scenari daemon se contiene una chiamata a AuthenticationContext.AcquireTokenAsync con i parametri seguenti:
- Una risorsa (URI ID app) come primo parametro
-
IClientAssertionCertificate o ClientAssertion come secondo parametro
AuthenticationContext.AcquireTokenAsync non ha un parametro di tipo UserAssertion. Se sì, la tua app è un'API web e usa lo scenario API web che chiama API web downstream.
Aggiornare il codice degli scenari daemon
I passaggi seguenti per l'aggiornamento del codice si applicano a tutti gli scenari client riservati:
- Aggiungi lo spazio dei nomi MSAL.NET nel codice sorgente:
using Microsoft.Identity.Client;.
- Anziché istanziare
AuthenticationContext, usare ConfidentialClientApplicationBuilder.Create per istanziare IConfidentialClientApplication.
- Anziché la stringa
resourceId, MSAL.NET usa gli scope. Poiché le applicazioni che usano ADAL.NET sono pre-autorizzate, è sempre possibile usare gli ambiti seguenti: new string[] { $"{resourceId}/.default" }.
- Sostituisci la chiamata a
AuthenticationContext.AcquireTokenAsync con una chiamata a IConfidentialClientApplication.AcquireTokenXXX, dove XXX dipende dal tuo scenario.
In questo caso, sostituire la chiamata a AuthenticationContext.AcquireTokenAsync con una chiamata a IConfidentialClientApplication.AcquireTokenClient.
Di seguito è riportato un confronto tra codice ADAL.NET e MSAL.NET per gli scenari daemon:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
public partial class AuthWrapper
{
const string ClientId = "Guid (AppID)";
const string authority
= "https://login.microsoftonline.com/{tenant}";
// App ID URI of web API to call
const string resourceId = "https://target-api.domain.com";
X509Certificate2 certificate = LoadCertificate();
public async Task<AuthenticationResult> GetAuthenticationResult()
{
var authContext = new AuthenticationContext(authority);
var clientAssertionCert = new ClientAssertionCertificate(
ClientId,
certificate);
var authResult = await authContext.AcquireTokenAsync(
resourceId,
clientAssertionCert,
);
return authResult;
}
}
using Microsoft.Identity.Client;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
public partial class AuthWrapper
{
const string ClientId = "Guid (Application ID)";
const string authority
= "https://login.microsoftonline.com/{tenant}";
// App ID URI of web API to call
const string resourceId = "https://target-api.domain.com";
X509Certificate2 certificate = LoadCertificate();
IConfidentialClientApplication app;
public async Task<AuthenticationResult> GetAuthenticationResult()
{
var app = ConfidentialClientApplicationBuilder.Create(ClientId)
.WithCertificate(certificate)
.WithAuthority(authority)
.Build();
// Setup token caching https://learn.microsoft.com/azure/active-directory/develop/msal-net-token-cache-serialization?tabs=aspnet
// For example, for an in-memory cache with 1GB limit, use
app.AddInMemoryTokenCache(services =>
{
// Configure the memory cache options
services.Configure<MemoryCacheOptions>(options =>
{
options.SizeLimit = 1024 * 1024 * 1024; // in bytes (1 GB of memory)
});
}
var authResult = await app.AcquireTokenForClient(
new [] { $"{resourceId}/.default" })
// .WithTenantId(specificTenant)
// See https://aka.ms/msal.net/withTenantId
.ExecuteAsync()
.ConfigureAwait(false);
return authResult;
}
}
Trarre vantaggio dalla memorizzazione nella cache dei token
Se non si configura la memorizzazione nella cache dei token, l'autorità emittente del token limita l'utente, generando errori. Richiede anche molto meno per ottenere un token dalla cache (10-20 ms) rispetto a ESTS (500-30000 ms).
Per implementare una cache di token distribuita, vedere Cache dei token per un'app Web o un'API Web (applicazione client riservata).
Altre informazioni sullo scenario del daemon e su come viene implementato con MSAL.NET o Microsoft. Identity.Web nelle nuove applicazioni.
Eseguire la migrazione di un'API Web che chiama le API Web downstream
Le API web che chiamano API web a valle utilizzano il flusso OAuth2.0 on-behalf-of (OBO). L'API Web usa il token di accesso recuperato dall'intestazione HTTP Authorize e convalida questo token. Questo token viene quindi scambiato con un token per chiamare l'API Web downstream. Questo token viene usato come istanza UserAssertion sia in ADAL.NET sia in MSAL.NET.
Scoprire se il codice usa OBO
Il codice ADAL per l'app usa OBO se contiene una chiamata a AuthenticationContext.AcquireTokenAsync con i parametri seguenti:
- Una risorsa (URI ID app) come primo parametro
-
IClientAssertionCertificate o ClientAssertion come secondo parametro
- Parametro di tipo
UserAssertion
Aggiornare il codice usando OBO
I passaggi seguenti per l'aggiornamento del codice si applicano a tutti gli scenari client riservati:
- Aggiungi il namespace MSAL.NET nel codice sorgente:
using Microsoft.Identity.Client;.
- Anziché creare un'istanza di
AuthenticationContext, usare ConfidentialClientApplicationBuilder.Create per creare un'istanza di IConfidentialClientApplication.
- Anziché la stringa
resourceId, MSAL.NET usa gli scope. Poiché le applicazioni che usano ADAL.NET sono pre-autorizzate, è sempre possibile usare gli ambiti seguenti: new string[] { $"{resourceId}/.default" }.
- Sostituire la chiamata a
AuthenticationContext.AcquireTokenAsync con una chiamata a IConfidentialClientApplication.AcquireTokenXXX, dove XXX dipende dallo scenario.
In questo caso, sostituiamo la chiamata a AuthenticationContext.AcquireTokenAsync con una chiamata a IConfidentialClientApplication.AcquireTokenOnBehalfOf.
Ecco un confronto tra codice OBO di esempio per ADAL.NET e MSAL.NET:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
public partial class AuthWrapper
{
const string ClientId = "Guid (AppID)";
const string authority
= "https://login.microsoftonline.com/common";
X509Certificate2 certificate = LoadCertificate();
public async Task<AuthenticationResult> GetAuthenticationResult(
string resourceId,
string tokenUsedToCallTheWebApi)
{
var authContext = new AuthenticationContext(authority);
var clientAssertionCert = new ClientAssertionCertificate(
ClientId,
certificate);
var userAssertion = new UserAssertion(tokenUsedToCallTheWebApi);
var authResult = await authContext.AcquireTokenAsync(
resourceId,
clientAssertionCert,
userAssertion,
);
return authResult;
}
}
using Microsoft.Identity.Client;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
public partial class AuthWrapper
{
const string ClientId = "Guid (Application ID)";
const string authority
= "https://login.microsoftonline.com/common";
X509Certificate2 certificate = LoadCertificate();
IConfidentialClientApplication app;
public async Task<AuthenticationResult> GetAuthenticationResult(
string resourceId,
string tokenUsedToCallTheWebApi)
{
var app = ConfidentialClientApplicationBuilder.Create(ClientId)
.WithCertificate(certificate)
.WithAuthority(authority)
.Build();
// Setup token caching https://learn.microsoft.com/azure/active-directory/develop/msal-net-token-cache-serialization?tabs=aspnet
// For example, for an in-memory cache with 1GB limit. For OBO, it is recommended to use a distributed cache like Redis.
app.AddInMemoryTokenCache(services =>
{
// Configure the memory cache options
services.Configure<MemoryCacheOptions>(options =>
{
options.SizeLimit = 1024 * 1024 * 1024; // in bytes (1 GB of memory)
});
}
var userAssertion = new UserAssertion(tokenUsedToCallTheWebApi);
var authResult = await app.AcquireTokenOnBehalfOf(
new string[] { $"{resourceId}/.default" },
userAssertion)
// .WithTenantId(specificTenant)
// See https://aka.ms/msal.net/withTenantId
.ExecuteAsync()
.ConfigureAwait(false);
return authResult;
}
}
Trarre vantaggio dalla memorizzazione nella cache dei token
Per la memorizzazione nella cache dei token negli OBO, usare una cache di token distribuita. Per informazioni dettagliate, vedere Cache dei token per un'app Web o un'API Web (app client riservata).
app.UseInMemoryTokenCaches(); // or a distributed token cache.
Scopri di più sulle API web che chiamano API web downstream e su come vengono implementate con MSAL.NET o Microsoft.Identity.Web nelle nuove app.
Eseguire la migrazione di un'app Web che chiama le API Web
Se l'app usa ASP.NET Core, è consigliabile eseguire l'aggiornamento a Microsoft. Identity.Web perché elabora tutto per l'utente. Per una rapida panoramica, vedi l'annuncio della disponibilità generale di Microsoft.Identity.Web. Per informazioni dettagliate su come usarlo in un'app Web, vedere Perché usare Microsoft. Identity.Web nelle app Web?.
Le app Web che autenticano gli utenti e chiamano API Web per loro conto usano il flusso del codice di autorizzazione di OAuth 2.0. Tipicamente:
- L'app consente l'accesso a un utente eseguendo la prima fase del flusso del codice di autorizzazione e indirizzandolo all'endpoint di autorizzazione di Microsoft Identity Platform. L'utente accede ed esegue le autenticazioni a più fattori, se necessario. Come risultato di questa operazione, l'app riceve il codice di autorizzazione. La libreria di autenticazione non viene usata in questa fase.
- L'app esegue la seconda parte del flusso del codice di autorizzazione. Usa il codice di autorizzazione per ottenere un token di accesso, un token ID e un token di aggiornamento. L'applicazione deve fornire il
redirectUri valore, ovvero l'URI in cui l'endpoint Microsoft Identity Platform fornirà i token di sicurezza. Dopo che l'app riceve tale URI, in genere chiama AcquireTokenByAuthorizationCode ADAL o MSAL per riscattare il codice e ottenere un token che verrà archiviato nella cache dei token.
- L'app usa ADAL o MSAL per chiamare
AcquireTokenSilent e ottenere i token necessari per chiamare le API web dai controller dell'app web.
Scoprire se il codice usa il flusso del codice di autenticazione
Il codice ADAL per l'app usa il flusso del codice di autenticazione se contiene una chiamata a AuthenticationContext.AcquireTokenByAuthorizationCodeAsync.
Aggiornare il codice usando il flusso del codice di autorizzazione
I passaggi seguenti per l'aggiornamento del codice si applicano a tutti gli scenari client riservati:
- Aggiungi il namespace MSAL.NET nel codice sorgente:
using Microsoft.Identity.Client;.
- Anziché creare un'istanza di
AuthenticationContext, usare ConfidentialClientApplicationBuilder.Create per creare un'istanza di IConfidentialClientApplication.
- Anziché la stringa
resourceId, MSAL.NET usa gli scope. Poiché le applicazioni che usano ADAL.NET sono pre-autorizzate, è sempre possibile usare gli ambiti seguenti: new string[] { $"{resourceId}/.default" }.
- Sostituire la chiamata a
AuthenticationContext.AcquireTokenAsync con una chiamata a IConfidentialClientApplication.AcquireTokenXXX, dove XXX dipende dallo scenario.
In questo caso, sostituire la chiamata a AuthenticationContext.AcquireTokenAsync con una chiamata a IConfidentialClientApplication.AcquireTokenByAuthorizationCode.
Ecco un confronto tra i flussi di codice di autorizzazione di esempio per ADAL.NET e MSAL.NET:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
public partial class AuthWrapper
{
const string ClientId = "Guid (AppID)";
const string authority
= "https://login.microsoftonline.com/common";
private Uri redirectUri = new Uri("host/login_oidc");
X509Certificate2 certificate = LoadCertificate();
public async Task<AuthenticationResult> GetAuthenticationResult(
string resourceId,
string authorizationCode)
{
var ac = new AuthenticationContext(authority);
var clientAssertionCert = new ClientAssertionCertificate(
ClientId,
certificate);
var authResult = await ac.AcquireTokenByAuthorizationCodeAsync(
authorizationCode,
redirectUri,
clientAssertionCert,
resourceId,
);
return authResult;
}
}
using Microsoft.Identity.Client;
using Microsoft.Identity.Web;
using System;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
public partial class AuthWrapper
{
const string ClientId = "Guid (Application ID)";
const string authority
= "https://login.microsoftonline.com/{tenant}";
private Uri redirectUri = new Uri("host/login_oidc");
X509Certificate2 certificate = LoadCertificate();
public IConfidentialClientApplication CreateApplication()
{
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create(ClientId)
.WithCertificate(certificate)
.WithAuthority(authority)
.WithRedirectUri(redirectUri.ToString())
.WithLegacyCacheCompatibility(false)
.Build();
// Add a token cache. For details about other serialization
// see https://aka.ms/msal-net-cca-token-cache-serialization
app.AddInMemoryTokenCache();
return app;
}
// Called from 'code received event'.
public async Task<AuthenticationResult> GetAuthenticationResult(
string resourceId,
string authorizationCode)
{
IConfidentialClientApplication app = CreateApplication();
var authResult = await app.AcquireTokenByAuthorizationCode(
new[] { $"{resourceId}/.default" },
authorizationCode)
.ExecuteAsync()
.ConfigureAwait(false);
return authResult;
}
}
La chiamata AcquireTokenByAuthorizationCode aggiunge un token alla cache dei token quando viene ricevuto il codice di autorizzazione. Per ottenere token aggiuntivi per altre risorse o tenant, usa AcquireTokenSilent nei tuoi controller.
public partial class AuthWrapper
{
// Called from controllers
public async Task<AuthenticationResult> GetAuthenticationResult(
string resourceId2,
string authority)
{
IConfidentialClientApplication app = CreateApplication();
AuthenticationResult authResult;
var scopes = new[] { $"{resourceId2}/.default" };
var account = await app.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
try
{
// try to get an already cached token
authResult = await app.AcquireTokenSilent(
scopes,
account)
// .WithTenantId(specificTenantId)
// See https://aka.ms/msal.net/withTenantId
.ExecuteAsync().ConfigureAwait(false);
}
catch (MsalUiRequiredException)
{
// The controller will need to challenge the user
// including asking for claims={ex.Claims}
throw;
}
return authResult;
}
}
Trarre vantaggio dalla memorizzazione nella cache dei token
Poiché l'app Web usa AcquireTokenByAuthorizationCode, deve usare una cache di token distribuita per il caching dei token. Per informazioni dettagliate, vedere Cache dei token per un'app Web o un'API Web.
app.UseInMemoryTokenCaches(); // or a distributed token cache.
Gestione di MsalUiRequiredException
Quando il controller tenta di acquisire un token in modo non interattivo per scope/risorse diversi, MSAL.NET potrebbe generare l'eccezione MsalUiRequiredException come previsto se l'utente deve effettuare nuovamente l'accesso o se l'accesso alla risorsa richiede claim aggiuntivi (a causa di un criterio di accesso condizionale). Per informazioni dettagliate sulla mitigazione, vedere come gestire gli errori e le eccezioni in MSAL.NET.
Altre informazioni sulle app Web che chiamano API Web e su come vengono implementate con MSAL.NET o Microsoft. Identity.Web nelle nuove applicazioni.
Vantaggi di MSAL
I vantaggi principali delle MSAL.NET per l'app includono:
Resilienza. MSAL.NET consente di rendere resiliente l'app tramite:
- Vantaggi del servizio Credenziali memorizzate nella cache (CCS) di Microsoft Entra ID. CCS funziona come backup di Microsoft Entra.
- Rinnovo proattivo dei token se l'API chiamata abilita i token di lunga durata tramite la valutazione continua dell'accesso.
Security. È possibile acquisire token di verifica del possesso (PoP) se l'API Web che si vuole chiamare lo richiede. Per informazioni dettagliate, vedere Token di prova del possesso in MSAL.NET
Prestazioni e scalabilità. Se non è necessario condividere la cache con ADAL.NET, disabilitare la compatibilità della cache legacy quando si crea l'applicazione client riservata (.WithLegacyCacheCompatibility(false)) per migliorare significativamente le prestazioni.
app = ConfidentialClientApplicationBuilder.Create(ClientId)
.WithCertificate(certificate)
.WithAuthority(authority)
.WithLegacyCacheCompatibility(false)
.Build();
Troubleshooting
MsalServiceException
Le seguenti informazioni sulla risoluzione dei problemi fanno due presupposti:
- Il codice ADAL.NET funzionava.
- È stata eseguita la migrazione a MSAL mantenendo lo stesso ID client.
Se viene visualizzata un'eccezione con uno dei messaggi seguenti:
AADSTS700027: Client assertion contains an invalid signature. [Reason - The key was not found.]
AADSTS90002: Tenant 'aaaabbbb-0000-cccc-1111-dddd2222eeee' not found. This may happen if there are no active
subscriptions for the tenant. Check to make sure you have the correct tenant ID. Check with your subscription
administrator.
Risolvere i problemi relativi all'eccezione attenendosi alla procedura seguente:
- Verificare di usare la versione più recente di MSAL.NET.
- Verifica che l'host dell'autorità impostato quando hai creato l'app client confidenziale e l'host dell'autorità utilizzato con ADAL siano simili. In particolare, è lo stesso cloud (Azure per enti pubblici, Microsoft Azure gestito da 21Vianet o Azure Germania)?
MsalClientException
Nelle app multi-tenant specificare un'autorità comune quando si compila l'app per specificare un tenant specifico, ad esempio il tenant dell'utente quando si chiama un'API Web. Poiché MSAL.NET 4.37.0, quando si specifica .WithAzureRegion al momento della creazione dell'app, non è più possibile specificare l'autorità usando .WithAuthority durante le richieste di token. In questo caso, si riceverà l'errore seguente durante l'aggiornamento dalle versioni precedenti di MSAL.NET:
MsalClientException - "You configured WithAuthority at the request level, and also WithAzureRegion. This is not supported when the environment changes from application to request. Use WithTenantId at the request level instead."
Per risolvere questo problema, sostituire .WithAuthority nell'espressione AcquireTokenXXX da .WithTenantId. Specificare il tenant usando un GUID o un nome di dominio.
Passaggi successivi
Altre informazioni su: