Skip to content

on behalf of

henrikm edited this page May 30, 2019 · 35 revisions

This page is for MSAL 2.3 and later

If you are interested in MSAL 2.x, please see on behalf of 2.x

Getting tokens on behalf of a user (Service to service calls)

Scenario

  • A client (Web, desktop, mobile, Single-page application) - not represented on the picture below - calls a protected Web API, providing a JWT bearer token in its "Authorization" Http Header.
  • The protected Web API validates the token, and uses MSAL.NET AcquireTokenOnBehalfOf method to request, to Azure AD, another token so that it can, itself, call a second Web API (named the downstream Web API) on behalf of the user.
  • The protected Web API uses this token to call a downstream API, it can also later call AcquireTokenSilent to request tokens for other downstream APIs (but still on behalf of the same user). AcquireTokenSilent refreshes the token when needed.

This flow, named the on-behalf-of flow (OBO), is illustrated by the top part of the picture below. The bottom part is a daemon scenario, also possible for Web APIs.

image

Registration of application secret or certificate with Azure AD

You can register your application secrets either through the interactive experience in the Azure portal, or using command-line tools (like PowerShell)

Registering client secrets using the application registration portal

The management of client credentials happens in the certificates & secrets page for an application:

image

  • the application secret (also named client secret) is generated by Azure AD during the registration of the confidential client application when you select New client secret. At that point, you must copy the secret string in the clipboard for use in your app, before selecting Save. This string won't be presented any longer.
  • the certificate is uploaded in the application registration using the Upload certificate button

Registering client secrets using PowerShell

The active-directory-dotnetcore-daemon-v2 sample shows how to register an application secret or a certificate with an Azure AD application:

Construction of ConfidentialClientApplication with client credentials

This flow is only available in the confidential client flow; therefore the protected Web API provides client credentials (client secret or certificate) to the ConfidentialClientApplicationBuilder via the or the WithClientSecret or WithCertificate methods respectively.

image

IConfidentialClientApplication app;

#if !VariationWithCertificateCredentials
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
           .WithClientSecret(config.ClientSecret)
           .Build();
#else
// Building the client credentials from a certificate
X509Certificate2 certificate = ReadCertificate(config.CertificateName);
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
    .WithCertificate(certificate)
    .Build();
#endif

How to call OBO

The OBO call is done by calling the AcquireTokenOnBehalf method on the IConfidentialClientApplication interface.

The UserAssertion is built from the bearer token received by the Web API from its own clients. There are two constructors, one taking a JWT bearer token, and one taking any kind of user assertion (another kind of security token, which type is then specified in an additional parameter named assertionType)

image

In practice, the OBO flow is often used to acquire a token for a downstream API, and store it in the MSAL.NET user token cache, so that other parts of the Web API can, later call on of the overrides of AcquireTokenOnSilent to call the downstream APIs (which also has the effect of refreshing the tokens if needed):

private void AddAccountToCacheFromJwt(IEnumerable<string> scopes, JwtSecurityToken jwtToken, ClaimsPrincipal principal, HttpContext httpContext)
{
 try
 {
  UserAssertion userAssertion;
  IEnumerable<string> requestedScopes;
  if (jwtToken != null)
  {
   userAssertion = new UserAssertion(jwtToken.RawData, "urn:ietf:params:oauth:grant-type:jwt-bearer");
   requestedScopes = scopes ?? jwtToken.Audiences.Select(a => $"{a}/.default");
  }
  else
  {
   throw new ArgumentOutOfRangeException("tokenValidationContext.SecurityToken should be a JWT Token");
  }

  // Create the application
  var application = BuildConfidentialClientApplication(httpContext, principal);

  // .Result to make sure that the cache is filled-in before the controller tries to get access tokens
  var result = application.AcquireTokenOnBehalfOf(requestedScopes.Except(scopesRequestedByMsalNet),
                                                  userAssertion)
                                        .ExecuteAsync()
                                        .GetAwaiter().GetResult();
 }
 catch (MsalException ex)
 {
  Debug.WriteLine(ex.Message);
  throw;
 }
}

Practical usage of OBO in an ASP.NET / ASP.NET Core application

In an ASP.NET / ASP.NET Core Web API, OBO is typically called on the OnTokenValidated event of the JwtBearerOptions. The token is then not used immediately, but this call has the effect of populating the user token cache. Later, the controllers will call AcquireTokenSilent, which will have the effect of hitting the cache, refreshing the access token if needed, or getting a new one for a new resource, but for still for the same user.

Here is what happens when a Jwt bearer token is received end validated by the Web API:

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

And here is the code in the actions of the API controllers, calling downstream APIs:

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.
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

// Call the To Do list service.
HttpResponseMessage response = await _httpClient.GetAsync(TodoListBaseAddress + "/api/todolist");
...
}

the GetAccountIdentifier method uses the claims associated with the identity of the user for which the Web API received the 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;
}

App registration - specificities for Web APIs

Protocol

For more information about the on-behalf-of protocol, see Azure Active Directory v2.0 and OAuth 2.0 On-Behalf-Of flow

Samples illustrating the on-behalf of flow

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 calling Microsoft Graph, itself called from a WPF application using Azure AD V2 topology

Vanity URL: https://aka.ms/msal-net-on-behalf-of

Getting started with MSAL.NET

Acquiring tokens

Web Apps / Web APIs / daemon apps

Desktop/Mobile apps

Advanced topics

FAQ

Other resources

Clone this wiki locally