-
Notifications
You must be signed in to change notification settings - Fork 374
on behalf of
This page is for MSAL 2.3 and later
If you are interested in MSAL 2.x, please see on behalf of 2.x
- 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.
You can register your application secrets either through the interactive experience in the Azure portal, or using command-line tools (like PowerShell)
The management of client credentials happens in the certificates & secrets page for an application:
- 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
The active-directory-dotnetcore-daemon-v2 sample shows how to register an application secret or a certificate with an Azure AD application:
- For details on how to register an application secret, see AppCreationScripts/Configure.ps1
- For details on how to register a certificate with the application, see AppCreationScripts-withCert/Configure.ps1
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.
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
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
)
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;
}
}
In order to prove their identity, confidential client applications exchange a secret with Azure AD. This can be a:
- a client secret (application password),
- a certificate, which is really used to build a signed assertion containing standard claims. This can also be a signed assertion directly.
MSAL.NET 4.1 adds a new capabilities for this advanced scenario: in addition to .WithClientSecret()
and .WithCertificate()
, it now provides two new methods: .WithClientAssertion()
and .WithClientClaims()
.
The first method takes a signed client assertion. It's expected to be a Base64 encoding of a JWT token which needs to contain mandatory claims. To use it:
string signedClientAssertion = ComputeAssertion();
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithClientAssertion(signedClientAssertion)
.Build();
The claims expected by Azure AD are:
Claim type | Value | Description |
---|---|---|
aud | https://login.microsoftonline.com/{tenantId}/v2.0 | The "aud" (audience) claim identifies the recipients that the JWT is intended for (here Azure AD) See [RFC 7519, Section 4.1.3] |
exp | Thu Jun 27 2019 15:04:17 GMT+0200 (Romance Daylight Time) | The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. See [RFC 7519, Section 4.1.4] |
iss | {ClientID} | The "iss" (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The "iss" value is a case-sensitive string containing a StringOrURI value. [RFC 7519, Section 4.1.1] |
jti | (a Guid) | The "jti" (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The "jti" claim can be used to prevent the JWT from being replayed. The "jti" value is a case-sensitive string. [RFC 7519, Section 4.1.7] |
nbf | Thu Jun 27 2019 14:54:17 GMT+0200 (Romance Daylight Time) | The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. [RFC 7519, Section 4.1.5] |
sub | {ClientID} | The "sub" (subject) claim identifies the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The See [RFC 7519, Section 4.1.2] |
WithClientClaims(X509Certificate2 certificate, IDictionary<string, string> claimsToSign, bool mergeWithDefaultClaims = true) by default will produce a signed assertion containing the claims expected by Azure AD plus additional client claims that you want to send. Here is a code snippet on how to do that.
string ipAddress = "192.168.1.2";
X509Certificate2 certificate = ReadCertificate(config.CertificateName);
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithAuthority(new Uri(config.Authority))
.WithClientClaims(certificate,
new Dictionary<string, string> { { "client_ip", ipAddress } })
.Build();
If one of the claims in the dictionary that you pass in is the same as one of the mandatory claims, the additional claims's value will be taken into account (it will override the claims computed by MSAL.NET)
If you want to provide your own claims, including the mandatory claims expected by Azure AD, simply pass in a false for the mergeWithDefaultClaims parameter.
The MsalError class was augmented with the following error related to client credentials
public static class MsalError
{
...
public const string ClientCredentialAuthenticationTypesAreMutuallyExclusive = "Client_Credential_Authentication_Types_Are_Mutually_Exclusive";
}
This error will be thrown if you attempt to use several client credentials (certificate, client secret, Signed claims, signed assertion)
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;
}
-
Web APIs expose scopes. For more information, see Quickstart: Configure an application to expose web APIs (Preview)
-
Web APIs decide which version of token they want to accept. For your own Web API, you can change the property of the manifest named
acceptedTokenVersion
(to 1 or 2). For more information, see Azure Active Directory app manifest
For more information about the on-behalf-of protocol, see Azure Active Directory v2.0 and OAuth 2.0 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 ![]() |
Vanity URL: https://aka.ms/msal-net-on-behalf-of
- Home
- Why use MSAL.NET
- Is MSAL.NET right for me
- Scenarios
- Register your app with AAD
- Client applications
- Acquiring tokens
- MSAL samples
- Known Issues
- Acquiring a token for the app
- Acquiring a token on behalf of a user in Web APIs
- Acquiring a token by authorization code in Web Apps
- AcquireTokenInteractive
- WAM - the Windows broker
- .NET Core
- Maui Docs
- Custom Browser
- Applying an AAD B2C policy
- Integrated Windows Authentication for domain or AAD joined machines
- Username / Password
- Device Code Flow for devices without a Web browser
- ADFS support
- High Availability
- Regional
- Token cache serialization
- Logging
- Exceptions in MSAL
- Provide your own Httpclient and proxy
- Extensibility Points
- Clearing the cache
- Client Credentials Multi-Tenant guidance
- Performance perspectives
- Differences between ADAL.NET and MSAL.NET Apps
- PowerShell support
- Testing apps that use MSAL
- Experimental Features
- Proof of Possession (PoP) tokens
- Using in Azure functions
- Extract info from WWW-Authenticate headers
- SPA Authorization Code