För flöden med MSAL.NET

Om du använder ASP.NET Core eller ASP.NET klassisk

Om du skapar ett webb-API ovanpå ASP.NET Core eller ASP.NET klassiskt rekommenderar vi att du använder Microsoft.Identity.Web. Se Webb-API:er med Microsoft.Identity.Web.

Kontrollera beslutsträdet: Är MSAL.NET rätt för mig?

Hämta token för en användares räkning

Scenario

  • En klient (webbplats, skrivbord, mobilt, ensidesprogram) – som inte visas i bilden nedan – anropar ett skyddat webb-API som tillhandahåller en JWT-ägartoken i HTTP-huvudet "Authorization".
  • Det skyddade webb-API:et verifierar den inkommande användartoken och använder MSAL.NET AcquireTokenOnBehalfOf metod för att begära från Microsoft Entra en annan token så att den kan anropa ett annat webb-API, till exempel Graph, med namnet underordnat webb-API, för användarens räkning.

Det här flödet, med namnet On-Behalf-Of flow (OBO), illustreras av den övre delen av bilden nedan. Den nedre delen är ett daemonscenario, även möjligt för webb-API:er.

bild

Så här anropar du OBO

OBO-anropet görs genom att anropa AcquireTokenOnBehalfOf(IEnumerable<String>, UserAssertion) metoden i IConfidentialClientApplication gränssnittet.

Det här anropet söker i cachen av sig självt, så du behöver inte anropa AcquireTokenSilent, och det lagrar inte uppdateringstoken.

För scenarier där kontinuerlig åtkomst behövs utan en försäkran, se OBO för långlivade processer

Observera: Se till att skicka en åtkomsttoken, inte en ID-token, till AcquireTokenOnBehalfOf metoden. Syftet med en ID-token är en bekräftelse på att en användare har autentiserats och innehåller viss användarrelaterad information. Däremot avgör en åtkomsttoken om en användare har åtkomst till en resurs, vilket är mer lämpligt i det här On-Behalf-Of-scenariot. MSAL fokuserar på att erhålla giltiga åtkomsttoken. ID-token hämtas också och cachelagras, men deras förfallodatum spåras inte. Så en ID-token kan upphöra att gälla och AcquireTokenSilent uppdaterar den inte.

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

Viktig anmärkning om On-Behalf-Of-flödet (OBO) med gästanvändare

När du utför OBO-flödet (On-Behalf-Of), särskilt med gästanvändare, är det viktigt att rikta sig mot den specifika klientorganisationen, som anges av claimet tid i klienttokenet. Använd inte /common eller /organizations i OBO, eftersom tokenen kommer att vara för användarens hemklientorganisation.

Korrekt användningsmönster

  1. Extrahera tid-claimet från klientens assertion-token: Detta identifierar den specifika klientorganisationen.
  2. Använd den klientorganisationsspecifika auktoriteten: Skapa auktoritets-URL:en med det extraherade tid-anspråket.

Felaktigt mönster

Många implementeringar använder felaktigt slutpunkten /common för att utföra OBO. Den här metoden rekommenderas inte och kan leda till problem, särskilt med gästanvändare.

Scenario för misslyckande

Det är ett vanligt scenario att en klientadministratör begränsar åtkomsten till det underordnade API:et (till exempel graph) genom att kräva att slutanvändarna slutför en MFA-utmaning (Multi-Factor Authentication). De använder dock ofta inte samma begränsningar för webb-API:et.

  1. Klienten (t.ex. skrivbordsapp eller webbplats) ber om en token för webb-API:et. MFA tillämpas inte just nu.
  2. Webb-API:t försöker byta den här tokenen mot en token för nedströms webb-API:t (t.ex. Graph) via on-behalf-of-flödet. Detta misslyckas eftersom åtkomst via Graph kräver att användaren har slutfört MFA-utmaningen. Anropet till AcquireTokenOnBehalfOf misslyckas med ett MsalUiRequiredException som också har Claims egenskapen inställd.

Så här signalerar du att MFA behövs till klienten

Webb-API:t behöver skicka tillbaka undantaget till klienten med strängen med anspråk. Standardmönstret för att signalera det här felet till en klient är att svara med HTTP 401 och med en WWW-Authenticate rubrik som kapslar in information om felet.

Webb-API:et svarar med 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}";
}

Hantera felet på klientsidan

Klienten måste tolka 401 meddelanden och parsa WWW-Authenticate rubriker. MSAL.NET erbjuder parsning av API:er:

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

Långvariga OBO-processer

Ett OBO-scenario är när ett webb-API kör långvariga processer åt användaren (till exempel OneDrive som skapar album åt dig). Detta kan implementeras så här:

  1. Innan du påbörjar en långvarig process, anropa:
string sessionKey = // custom key or null
var authResult = await ((ILongRunningWebApi)confidentialClientApp)
         .InitiateLongRunningProcessInWebApi(
              scopes,
              userAccessToken,
              ref sessionKey)
         .ExecuteAsync();

userAccessToken är en användaråtkomsttoken som används för att anropa det här webb-API:et. sessionKey används som en nyckel vid cachelagring och hämtning av OBO-token. Om den är inställd på null, anger MSAL det till assertionshashen för den angivna användartoken. Det kan också ställas in av utvecklaren på något som identifierar en specifik användarsession, till exempel det valfria sid anspråket från användartoken (mer information finns i Ange valfria anspråk till din app). InitiateLongRunningProcessInWebApikontrollerar inte cachen. den använder användartoken för att hämta en ny OBO-token från Microsoft Entra ID, som sedan cachelagras och returneras.

  1. I den långvariga processen, när en OBO-token behövs, anropar du AcquireTokenInLongRunningProcess enligt följande mönster:
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}";
}

Skicka sessionKey, som är associerat med den aktuella användarsessionen och kommer att användas för att hämta den relaterade OBO-tokenen. Om tokenen har upphört att gälla använder MSAL den cachelagrade uppdateringstoken för att hämta en ny OBO-åtkomsttoken från Microsoft Entra ID och cachelagra den. Om ingen token hittas med detta sessionKeygenererar MSAL en MsalClientException eller en MsalUiRequiredException. Se till att hämta en giltig användartoken och anropa InitiateLongRunningProcessInWebApi om så är fallet.

Cacheutmatning för långvariga OBO-processer

Vi rekommenderar starkt att du använder en distribuerad bevarad cache i ett webb-API-scenario. Eftersom dessa API:er lagrar uppdateringstoken föreslår MSAL inte någon förfallotid eftersom uppdateringstoken har en lång livslängd och kan användas om och om igen.

Vi rekommenderar att du anger borttagningsprinciper för L1 och L2 manuellt, till exempel en maxstorlek för L1-cachen och en glidande förfallotid för L2.

Hantering av undantag

Om ett AcquireTokenInLongRunningProcess undantag uppstår när det inte går att hitta en token och L2-cachen har en cachepost för samma cachenyckel kontrollerar du att L2-cacheläsningsåtgärden har slutförts. AcquireTokenInLongRunningProcess skiljer sig från InitiateLongRunningProcessInWebApi och AcquireTokenOnBehalfOf på så sätt att om cacheläsningen misslyckas kan den här metoden inte hämta en ny token från Microsoft Entra ID, eftersom den inte har något ursprungligt användarintyg. Om du använder Microsoft. Identity.Web.TokenCache för att aktivera distribuerad cache anger du OnL2CacheFailure-händelsen för att försöka utföra L2-anropet igen och/eller lägga till extra loggar, som kan aktiveras via inbyggda MSAL-funktioner.

Ta bort konton

Från och med MSAL 4.51.0 kan du ta bort cachelagrade token genom att anropa StopLongRunningProcessInWebApiAsync och skicka med en cachenyckel. I tidigare versioner av MSAL rekommenderas att använda utrensningsprinciper för L2-cache. Om omedelbar borttagning krävs tar du bort den L2-cachenod som är associerad med sessionKey.

Troubleshooting

Om du uppdaterar MSAL.NET till 4.51.0+ finns det en risk att InitiateLongRunningProcessInWebApi slutar returnera token och utlöser ett undantag om du förlitar dig på att den ska returnera token när den långvariga processen redan har startats och det finns en token i cachen för den angivna cachenyckeln. InitiateLongRunningProcessInWebApi inspekterar inte längre cachen för att hämta token. Använd AcquireTokenInLongRunningProcess för att fortsätta få åtkomst till den långvariga process som för närvarande är aktiv. InitiateLongRunningProcessInWebApi Ska endast användas för att initiera processen. Om det inte går att göra dessa ändringar snabbt och du uppdaterar till MSAL 4.54.1 eller senare kan du använda InitiateLongRunningProcessInWebApi().WithSearchInCacheForLongRunningProcess() för att återställa beteendet för InitiateLongRunningProcessInWebApi

Ändringar i appregistrering

Praktisk användning av OBO i ett ASP.NET/ASP.NET Core program

Om du skapar ett webb-API ovanpå ASP.NET Core rekommenderar vi att du använder Microsoft.Identity.Web. Se webb-API:er med Microsoft.Identity.Web.

I ett ASP.NET/ASP.NET Core-webb-API anropas OBO vanligtvis för OnTokenValidated händelsen JwtBearerOptions. Token används sedan inte omedelbart, men det här anropet har effekten att fylla i cacheminnet för användartoken. Senare kommer styrenheterna att anropa AcquireTokenSilent, vilket innebär att cachen används, att åtkomsttoken uppdateras vid behov eller att en ny hämtas för en ny resurs, men fortfarande för samma användare.

Så här händer när en JWT-ägartoken tas emot och verifieras av webb-API:et:

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

Och här är koden i åtgärderna för API-kontrollanterna, som anropar underordnade API:er:

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

Metoden GetAccountIdentifier använder anspråken som är associerade med identiteten för den användare som webb-API:et tog emot JWT för:

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

Protokoll

Mer information om on-Behalf-Of-protokollet finns i Azure Active Directory v2.0 och OAuth 2.0 On-Behalf-Of flow.

Exempel som illustrerar flödets räkning

Sample Platform Description
active-directory-aspnetcore-webapi-tutorial-v2 ASP.NET Core 2.2 Web API, Desktop (WPF) ASP.NET Core 2.1 Web API anropar Microsoft Graph, som anropas från ett WPF-program med hjälp av Azure AD v2 För flödestopologi