-
Notifications
You must be signed in to change notification settings - Fork 411
How ASP.NET Core uses Microsoft.IdentityModel extensions for .NET
This article discusses the interactions between ASP.NET Core and Microsoft.IdentityModel extensions for .NET
ASP.NET Core enables you to create:
- Web applications, signing-in users (and possibly calling Web APIs)
- Protected Web APIs (possibly calling Web APIs)
ASP.NET Core (like ASP.NET) leverages the notion of Middleware. Depending on the type of app above, the middleware you'll use will be different:
- OpenIdConnect middleware for Web Apps sign-ing in users
- JwtBearer for protected Web APIs
In any case, ASP.NET Core will validate token using Microsoft.IdentityModel extensions for .NET, and you can control what aspects of the security tokens you want to validate using the TokenValidationParameters class.
In the cases where ASP.NET Core needs to sign-in users, it will call Azure AD (or another Security Token Service), and for this it will leverage the OpenIdConnectMessage. It will send such a message to the STS, and will receive another message from the STS. Then from this message will progressively be transformed, extracting the identity of the user (into a ClaimsPrincipal object).
An example of ASP.NET Core app sign-in users with Azure AD is available from the following sample: active-directory-aspnetcore-webapp-openidconnect-v2. In particular, in the Startup.cs class, you'll notice the ConfigureServices method. This methods, between other things calls services.Configure<OpenIdConnectOptions>()
and this method has an argument options
(of type OpenIdConnectOptions
which is an ASP.NET Core security concept). options
has a property named TokenValidationParameters
, of type TokenValidationParameters, which is from this library.
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
. . .
// If you want to restrict the users that can sign-in to several organizations
// Set the tenant value in the appsettings.json file to 'organizations', set
// ValidateIssuer, above to 'true', and add the issuers you want to accept to the
// options.TokenValidationParameters.ValidIssuers collection
options.TokenValidationParameters.ValidateIssuer = false;
});
You can control and understand how an OpenIdConnectMessage is progressively transformed to an identity and security tokens
options
also has a property named Events
which surfaces how:
-
ASP.NET builds-up an OpenIdConnectMessage to send a request to Azure AD
You have an opportunity to intercept this call and update the OpenIdConnectMessage by adding a handler to the
OnRedirectToIdentityProvider(context)
event. This can be useful, for instance, if you want to enable single-sign-on, by providing login_hint and domain_hint, which will avoid Azure AD to display the select account dialog. Note that the context parameter of each event has members, some of which are Identity Model Extensions for .NET types (SecurityToken, OpenIdConnectMessage, etc …) -
It receives back another OpenIdConnectMessage. You can observe it by subscribing to the
OnMessageReceived
event.
Depending on the options.ResponseType
, the following events might be triggered:
-
OnTokenValidation
, in the case whereoptions.ResponseType
containsid_token
. Typically the token validation involves the TokenValidationParameters set in the previous paragraph. -
OnAuthorizationCodeReceived
whenoptions.ResponseType
containscode
. This gives your application to redeem the code (for instance using MSAL.NET), and keep the access token and refresh token in the cache, so that it can be used later from ASP.NET Core Controllers. -
OnTokenResponseReceived
whenoptions.ResponseType
containstoken
. This happens when you let ASP.NET Core redeeming the code. You will typically not do that if you handle the code redemption yourself inOnAuthorizationCodeReceived
as you will have stopped further processing from happening by callingcontext.HandleCodeRedemption()
. -
OnAuthenticationFailed
will be called any time the authentication has failed (be it because of the token validation or not)
There are another set of events which are about sign-out:
-
OnRedirectToIdentityProviderForSignOut
is similar asOnRedirectToIdentityProvider
, but for the Sign-out process, whereas this one is for the sign-in process. -
OnRemoteSignOut
is called when Azure AD (or the STS in general) has done the sign-out.
If you want to see how this pipeline is used to let a Web App calling a Web API on behalf of the signed-in user, look at the following
If you want to understand how the pipeline works by seing traces, or setting breakpoints in event handlers, look at this PR: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/pull/35, which implements a few diagnostics for each event.
The same kind of customization and pipeline exists in a protected Web API. For more details, see the following sample: https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2
This sample contains a progressive tutorial made of two parts:
Sub folder | Description |
---|---|
1. Desktop app calls Web API | This first part, presents an ASP.NET Core 2.1 Web API protected by Azure Active Directory OAuth Bearer Authentication. This Web API is exercised by a .NET Desktop WPF application. This subfolder contains a Visual Studio solution made of two applications: the desktop application (TodoListClient), and the Web API (TodoListService) |
2. Web API now calls Microsoft Graph | The second part presents an increment where the Web API now calls Microsoft Graph on-behalf of the user signed-in in the desktop application. In this part, the Web API uses the Microsoft Authentication Library for .NET (MSAL.NET) to acquire a token for Microsoft Graph using the on-behalf-of flow. |
The same kind of principles apply for Web APIs, replacing the OpenIdConnectOptions
by JwtBearerOptions
and OpenIdConnectEvents
by JwtBearerEvents
:
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
// This is an Azure AD v2.0 Web API
options.Authority += "/v2.0";
// The valid audiences are both the Client ID (options.Audience) and api://{ClientID}
options.TokenValidationParameters.ValidAudiences = new string[] { options.Audience, $"api://{options.Audience}" };
// Instead of using the default validation (validating against a single tenant, as we do in line of business apps),
// we inject our own multitenant validation logic (which even accepts both V1 and V2 tokens)
options.TokenValidationParameters.IssuerValidator = AadIssuerValidator.ValidateAadIssuer;
// When an access token for our own Web API is validated, we add it to MSAL.NET's cache so that it can
// be used from the controllers.
options.Events = new JwtBearerEvents();
options.Events.OnTokenValidated = async context =>
{
var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>();
var scopes = new string[] { "user.read" };
context.Success();
// Adds the token to the cache, and also handles the incremental consent and claim challenges
tokenAcquisition.AddAccountToCacheFromJwt(context, scopes);
};
Conceptual Documentation
- Using TokenValidationParameters.ValidateIssuerSigningKey
- Scenarios
- Validating tokens
- Outbound policy claim type mapping
- How ASP.NET Core uses Microsoft.IdentityModel extensions for .NET
- Using a custom CryptoProvider
- SignedHttpRequest aka PoP (Proof-of-Possession)
- Creating and Validating JWEs (Json Web Encryptions)
- Caching in Microsoft.IdentityModel
- Resiliency on metadata refresh
- Use KeyVault extensions
- Signing key roll over