Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Si usa ASP.NET Core o ASP.NET clásico
Si va a compilar una API web sobre ASP.NET Core o ASP.NET clásico, se recomienda usar Microsoft.Identity.Web. Consulte las APIs web con Microsoft.Identity.Web.
Compruebe el árbol de decisión: ¿es MSAL.NET correcto para mí?
Obtención de tokens en nombre de un usuario
Scenario
- Un cliente (sitio web, escritorio, móvil, aplicación de página única), no representado en la imagen siguiente, llama a una API web protegida, proporcionando un token de portador JWT en su encabezado HTTP "Authorization".
- La API web protegida valida el token de usuario recibido y usa el método de MSAL.NET
AcquireTokenOnBehalfOfpara solicitar a Microsoft Entra otro token, de modo que pueda, a su vez, llamar a otra API web, por ejemplo Graph, denominada API web descendente, en nombre del usuario.
Este flujo, denominado «On-Behalf-Of» (OBO), se ilustra en la parte superior de la imagen siguiente. La parte inferior muestra un escenario de daemon, también posible para las API web.
Cómo llamar a OBO
La llamada OBO se realiza llamando al método AcquireTokenOnBehalfOf(IEnumerable<String>, UserAssertion) en la interfaz IConfidentialClientApplication.
Esta llamada busca en la caché por sí sola, así que no necesitas llamar a AcquireTokenSilent, y no almacena tokens de actualización.
Para ver escenarios en los que se necesita acceso continuo sin una aserción, consulte OBO para procesos de larga duración.
Nota: Asegúrese de pasar un token de acceso, no un token de identificador, al
AcquireTokenOnBehalfOfmétodo . El propósito de un token de identificador es una confirmación de que se ha autenticado un usuario y contiene información relacionada con el usuario. Por el contrario, un token de acceso determina si un usuario tiene acceso a un recurso, lo cual resulta más adecuado en este escenario «On-Behalf-Of». MSAL se centra en obtener tokens de acceso válidos. Los tokens de identificador también se obtienen y almacenan en caché, pero no se realiza un seguimiento de su expiración. Por lo tanto, un token de identificador puede expirar yAcquireTokenSilentno lo actualizará.
private async Task AddAccountToCacheFromJwt(IEnumerable<string> scopes, JwtSecurityToken jwtToken, ClaimsPrincipal principal, HttpContext httpContext)
{
if (jwtToken == null)
{
throw new ArgumentOutOfRangeException("tokenValidationContext.SecurityToken should be a JWT Token");
}
UserAssertion userAssertion = new UserAssertion(jwtToken.RawData, "urn:ietf:params:oauth:grant-type:jwt-bearer");
IEnumerable<string> requestedScopes = scopes ?? jwtToken.Audiences.Select(a => $"{a}/.default");
// Create the application
var application = BuildConfidentialClientApplication(httpContext, principal);
// await to make sure that the cache is filled in before the controller tries to get access tokens
var result = await application.AcquireTokenOnBehalfOf(requestedScopes, userAssertion).ExecuteAsync();
}
Nota importante sobre el flujo «On-Behalf-Of» (OBO) con usuarios invitados
Al realizar el flujo «On-Behalf-Of» (OBO), especialmente con usuarios invitados, es importante dirigirse al inquilino específico, indicado por la reclamación tid del token de cliente. No use /common o /organizations en OBO, ya que el token será para el inquilino principal del usuario.
Patrón de uso correcto
-
Extrae la reclamación
tiddel token de aserción del cliente: esto identifica al inquilino específico. -
Utilice la autoridad específica del inquilino : cree la URL de autoridad utilizando la reclamación
tidextraída.
Patrón incorrecto
Muchas implementaciones utilizan incorrectamente el endpoint /common para llevar a cabo OBO. Este enfoque no se recomienda y puede provocar problemas, especialmente con los usuarios invitados.
Control de la autenticación multifactor (MFA), el acceso condicional y el consentimiento incremental
Escenario de error
Es un escenario común que un administrador de inquilinos restringe el acceso a la API de bajada (por ejemplo, a Graph) al exigir a los usuarios finales que completen un desafío de Multi-Factor Authentication (MFA); sin embargo, a menudo no aplican las mismas restricciones en la API web.
- El cliente (por ejemplo, aplicación de escritorio o sitio web) solicita un token para la API web. MFA no se aplica en este momento.
- La API web intenta canjear este token por un token para la API web de nivel inferior (p. ej., Graph) a través del flujo «en nombre de». Esto produce un error porque el acceso a través de Graph requiere que el usuario haya completado el desafío de MFA. La llamada a
AcquireTokenOnBehalfOffallará con unMsalUiRequiredException, que también tendrá establecida la propiedadClaims.
Cómo indicar que se necesita MFA para el cliente
La API web debe enviar la excepción de vuelta al cliente con la cadena de declaraciones.
El patrón estándar para indicar este error a un cliente es responder con HTTP 401 y con un WWW-Authenticate encabezado que encapsula los detalles del error.
La API web responde con 401 + WWW-Authenticate
// This example is for an ASP.NET Core web API
public void ReplyUnauthorizedWithWwwAuthenticateHeader(MsalUiRequiredException ex)
{
httpResponse.StatusCode = (int)HttpStatusCode.Unauthorized; // HTTP 401
httpResponse.Headers[HeaderNames.WWWAuthenticate] = $"Bearer claims={ex.Claims}, error={ex.Message}";
}
Control del error en el cliente
El cliente debe interpretar 401 los mensajes y analizar WWW-Authenticate los encabezados. MSAL.NET ofrece API de análisis:
// assuming an HttpResponseMessage response with StatusCode=HttpStatusCode.Unauthorized
WwwAuthenticateParameters wwwParams = WwwAuthenticateParameters.CreateFromAuthenticationHeaders(response.HttpResponseHeaders, "Bearer");
string claims = wwwParams.Claims; // you may also extract other parameters such as Error and Authority
// desktop or mobile app
app.AcquireTokenInteractive(scopes).WithClaims(wwwParams.Claims);
// web app - redirect to the login page and add the claims to the authorization URL
RedirectToLogin(wwwParams.ConsentUri);
Procesos OBO de larga duración
Un caso de OBO se da cuando una API web ejecuta procesos de larga duración en nombre del usuario (por ejemplo, OneDrive, que crea álbumes por usted). Esto se puede implementar como tal:
- Antes de iniciar un proceso de ejecución prolongada, llame a:
string sessionKey = // custom key or null
var authResult = await ((ILongRunningWebApi)confidentialClientApp)
.InitiateLongRunningProcessInWebApi(
scopes,
userAccessToken,
ref sessionKey)
.ExecuteAsync();
userAccessToken es un token de acceso de usuario que se usa para llamar a esta API web.
sessionKey se usará como clave al almacenar en caché y al recuperar el token de OBO. Si se establece en null, MSAL la establecerá en el hash de aserción del token de usuario proporcionado. El desarrollador también puede establecerlo en algo que identifique una sesión de usuario específica, como la notificación opcional sid del token de usuario (para obtener más información, consulte Proporcionar notificaciones opcionales a la aplicación).
InitiateLongRunningProcessInWebApino comprueba la memoria caché; usará el token de usuario para adquirir un nuevo token de OBO de Microsoft Entra ID, que se almacenará en caché y se devolverá.
- En el proceso de larga duración, siempre que se necesite un token OBO, llame a
AcquireTokenInLongRunningProcesssiguiendo este patrón:
try {
authResult = await ((ILongRunningWebApi)confidentialClientApp)
.AcquireTokenInLongRunningProcess(
scopes,
sessionKey)
.ExecuteAsync();
}
catch (MsalClientException ex) {
// No tokens were found with this cache key.
// First call InitiateLongRunningProcessInWebApi with a valid user assertion
// to acquire tokens from Microsoft Entra ID and cache them.
if (ex.ErrorCode == MsalError.OboCacheKeyNotInCacheError)
{
authResult = await ((ILongRunningWebApi)confidentialClientApp)
.InitiateLongRunningProcessInWebApi(
scopes,
userAccessToken, // Valid access token
ref sessionKey)
.ExecuteAsync();
}
} catch (MsalUiRequiredException ex) {
// A refresh token was used to acquire new tokens
// but Microsoft Entra ID requires the user to sign in again.
// Trigger your app's user sign-in again by replying with a 401 + WWW-Authenticate
// Then call InitiateLongRunningProcessInWebApi once a new access token is acquired from the user
httpResponse.StatusCode = (int)HttpStatusCode.Unauthorized;
httpResponse.Headers[HeaderNames.WWWAuthenticate] = $"Bearer claims={ex.Claims}, error={ex.Message}";
}
Pase el sessionKey asociado a la sesión del usuario actual, que se utilizará para recuperar el token OBO correspondiente. Si el token ha expirado, MSAL usará el token de actualización en caché para adquirir un nuevo token de acceso de OBO de Microsoft Entra ID y almacenarlo en caché. Si no se encuentra ningún token con este sessionKey, MSAL lanzará una MsalClientException o una MsalUiRequiredException. Asegúrese de adquirir un token de usuario válido y llame a InitiateLongRunningProcessInWebApi si este es el caso.
Evicción de la caché para procesos OBO de larga duración
Se recomienda encarecidamente usar una caché distribuida persistente en un escenario de API web. Dado que estas API almacenan el token de actualización, MSAL no sugerirá una expiración, ya que los tokens de actualización tienen una larga duración y se pueden usar de nuevo.
Se recomienda establecer las directivas de expulsión L1 y L2 manualmente, por ejemplo, un tamaño máximo para la caché L1 y una expiración deslizante para el L2.
Control de excepciones
En un caso cuando AcquireTokenInLongRunningProcess produce una excepción cuando no encuentra un token y la caché L2 tiene una entrada de caché para la misma clave de caché, compruebe que la operación de lectura de caché L2 se ha completado correctamente.
AcquireTokenInLongRunningProcesses diferente de InitiateLongRunningProcessInWebApi y AcquireTokenOnBehalfOf, en que es si se produce un error en la lectura de caché, este método no puede adquirir un nuevo token de Microsoft Entra ID porque no tiene una aserción de usuario original. Si usa Microsoft. Identity.Web.TokenCache para habilitar la caché distribuida, establezca el evento OnL2CacheFailure para reintentar la llamada L2 o agregar registros adicionales, que se pueden habilitar mediante la funcionalidad integrada de MSAL.
Eliminación de cuentas
A partir de MSAL 4.51.0, para quitar los tokens almacenados en caché, llame a StopLongRunningProcessInWebApiAsync pasándole una clave de caché. Con versiones anteriores de MSAL, se recomienda usar directivas de expulsión de caché L2. Si se necesita la eliminación inmediata, elimine el nodo de caché L2 asociado a sessionKey.
Solución de problemas
Si va a actualizar MSAL.NET a la versión 4.51.0 o posterior, existe la posibilidad de que InitiateLongRunningProcessInWebApi deje de devolver tokens y produzca una excepción si depende de que le devuelva tokens una vez que el proceso de larga duración ya se haya iniciado y exista un token en la memoria caché para la clave de caché especificada.
InitiateLongRunningProcessInWebApi ya no inspecciona la memoria caché para adquirir tokens. Utilice AcquireTokenInLongRunningProcess para seguir accediendo al proceso de larga duración actualmente activo. Solo InitiateLongRunningProcessInWebApi se debe usar para iniciar el proceso. Si no es posible realizar estos cambios rápidamente y está actualizando a MSAL 4.54.1 o superior, puede usar InitiateLongRunningProcessInWebApi().WithSearchInCacheForLongRunningProcess() para revertir el comportamiento de . InitiateLongRunningProcessInWebApi
Cambios en el registro de aplicaciones
Las API web exponen alcances. Para obtener más información, consulte Inicio rápido: Configuración de una aplicación para exponer las API web (versión preliminar).
Las API web deciden qué versión del token quieren aceptar. Para su propia API web, puede cambiar la propiedad en el manifiesto denominado
accessTokenAcceptedVersion(a1o2). A menos que sepa explícitamente que necesita la versión1, elija2siempre . Para obtener más información, consulte Microsoft Entra manifiesto de aplicación.
Uso práctico de OBO en una aplicación de ASP.NET/ASP.NET Core
Si va a compilar una API web sobre ASP.NET Core, se recomienda usar Microsoft.Identity.Web. Consulte las API web con Microsoft.Identity.Web.
En una API web de ASP.NET / ASP.NET Core, OBO suele llamarse en el evento OnTokenValidated del JwtBearerOptions. A continuación, el token no se usa inmediatamente, pero esta llamada tiene el efecto de rellenar la caché del token de usuario. Más adelante, los controladores llamarán a AcquireTokenSilent, que tendrá el efecto de alcanzar la memoria caché, actualizar el token de acceso si es necesario o obtener uno nuevo para un nuevo recurso, pero todavía para el mismo usuario.
Esto es lo que sucede cuando la API web recibe y valida un token de portador JWT:
public static IServiceCollection AddProtectedApiCallsWebApis(this IServiceCollection services, IConfiguration configuration, IEnumerable<string> scopes)
{
...
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.Events.OnTokenValidated = async context =>
{
var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>();
context.Success();
// Adds the token to the cache, and also handles the incremental consent and claim challenges
tokenAcquisition.AddAccountToCacheFromJwt(context, scopes);
await Task.FromResult(0);
};
});
return services;
}
Y este es el código en las acciones de los controladores de API, llamando a las API de bajada:
private async Task GetTodoList(bool isAppStarting)
{
...
//
// Get an access token to call the To Do service.
//
AuthenticationResult result = null;
try
{
result = await _app.AcquireTokenSilent(Scopes, accounts.FirstOrDefault())
.ExecuteAsync()
.ConfigureAwait(false);
}
...
// Once the token has been returned by MSAL, add it to the http authorization header, before making the call to access the To Do list service.
// Make sure to use an access token and not an ID token
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
// Call the To Do list service.
HttpResponseMessage response = await _httpClient.GetAsync(TodoListBaseAddress + "/api/todolist");
...
}
El método GetAccountIdentifier utiliza las declaraciones asociadas a la identidad del usuario del que la Web API recibió el JWT:
public static string GetMsalAccountId(this ClaimsPrincipal claimsPrincipal)
{
string userObjectId = GetObjectId(claimsPrincipal);
string tenantId = GetTenantId(claimsPrincipal);
if (!string.IsNullOrWhiteSpace(userObjectId) && !string.IsNullOrWhiteSpace(tenantId))
{
return $"{userObjectId}.{tenantId}";
}
return null;
}
Protocol
Para obtener más información sobre el protocolo On-Behalf-Of, consulte Azure Active Directory v2.0 y OAuth 2.0 On-Behalf-Of flow.
Ejemplos que ilustran el flujo «en nombre de»
| Ejemplo | Platform | Description |
|---|---|---|
| active-directory-aspnetcore-webapi-tutorial-v2 | API web de ASP.NET Core 2.2, escritorio (WPF) | API web de ASP.NET Core 2.1 que llama a Microsoft Graph, a su vez llamada desde una aplicación WPF que utiliza Azure AD v2 ![]() |
