OAuth2 Client Credentials flow is a protocol to allow secure communication between two web APIs. Specifically, the protocol specifies the flow of obtaining authorization for a client to access protected endpoints of a resource server with no user interaction involved. With Microsoft Identity Platform, Azure portal, Microsoft Authentication Library (MSAL), and .NET core security middleware, you can implement the OAuth2 client credentials flow without much difficulty. In this post, I go over how to leverage those technologies to protect your ASP.NET core web APIs.
In an OAuth2 client credentials flow, when the client asks the authorization server for an access token, the client authenticates using it’s credentials and specifies the resource types (scopes) which it needs access. The authorization server issues an access token for the client to access the resource server upon successful authentication. In the Oauth2 client-credentials flow, Azure AD acts as an authorization server. As such, it needs to identify the client and resource server, know the scopes available, and whether the client has been granted access. Using azure portal, you can register the applications, specify the scopes and set up access necessary for AAD to support the client credentials flow.
As an example, I have two applications which I register in AAD, one represent the client, and the other the resource server.
The below animation show the steps to register an application in AAD. The client application needs to authenticate against Microsoft Identity Platform (v2.0) endpoint. For this example, I simply create a secret to use as the app’s password.
The below animation shows an example of registering the resource owner in a Client Credentials flow.
A scope in OAuth2 terminology represents a specific type of resource. In a different OAuth2 flow such as an implicit flow, an app can ask the user to grant access to a scope. For instance, the app may ask for Read and Write access to the user’s calendar in G Suite. Depending on the implementation, the user may selectively approve some scopes and deny others. For instance, the user may approve Read access and deny Write access. As shown in the above animation, via “Expose an API”, you can define the scopes for which the client can request access .
Whereas in an implicit flow, you can let the user approve or deny the scopes on demand,In a client credentials flow, you need to grant the client access in advance because the flow does not involve the user’s interaction. This is important to remember, since if you forget this step, you’ll get an error when making a request for an access token.
We have gone over the application registration. Let’s now go over the configuration in the applications.
In the codes for the client application, we are going to use the MSAL library to handle the authentication and obtaining an access token. Specifically, we are going to use the Microsoft.Identity.Client package.
The following code snippets authenticate against AAD and obtain an access token for the client to access the resource server.
var appOptions = configuration .GetSection(OptionKeys.PortalApiADPubOptions) .Get<ConfidentialClientApplicationOptions>(); string authority = $"{appOptions.Instance}{appOptions.TenantId}/"; var application = ConfidentialClientApplicationBuilder .CreateWithApplicationOptions(appOptions) .WithAuthority(authority).Build(); var result = await application.AcquireTokenForClient(scopes: scopes).ExecuteAsync();
The appsettings file contains the configurations including the client id and secret for the client to authenticate against AAD.
{
"AzureADApp": { "Instance": "https://login.microsoftonline.com/", "ClientId": "********-****-****-bd9e-6673a45b57f1", "ClientSecret": "*********.5Xe0dH1m8pWPO.bUQ4_HiA", "TenantId": "********-****-****-****-2283395ed452" }
}
The JSON above gets deserialized into an instance of ConfidentialClientApplicationOptions
.
The next section of the codes build an implementation of the IConfidentialClientApplication
using the options from the appsettings. We use the AcquireTokenForClient
method, passing in the scopes parameter. The scopes parameter is an array of strings which contain the full URI we define in the Expose an API section when we register the resource owner.
In the above code snippets, you see how we can obtain an access token for the client app. In the other application – the resource server which hosts the protected resources, we need to validate the access token. Let’s go over that in the next section.
We use ASP.NET core authentication middleware to check a request contains a valid JWT token before allowing access to the resources.
The following code adds the .NET core authentication middleware to the request pipeline.
app.UseAuthentication()
The app.UseAuthentication()
ensures the caller has already authenticated before allowing access to the resource and also set the User object. We need to set the authentication middleware before the Mvc middleware (app.UseMvc()
) since we want to make sure the caller has already authenticated before allowing access . Otherwise, the Authorization
tag does not work.
Besides adding the authentication middleware, you also need to register the services to handle the authentication. You can use the .NET Core JwtBearer middleware to handle receiving and validating a JWT token. For instance, below I show the code snippets for validating a JWT token.
public static IServiceCollection AddSecurity(this IServiceCollection services, IConfiguration configuration) {
AzureADOptions azureADOptions = configuration.GetSection("AzureADApp").Get<AzureADOptions>(); services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme).AddAzureADBearer(options => { configuration.Bind("AzureADApp", options); }); services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options => { // This is an Azure AD v2.0 Web API. options.Authority += "/v2.0"; options.TokenValidationParameters.ValidateTokenReplay = true; options.TokenValidationParameters.ValidIssuer = string.Format(@"https://sts.windows.net/{0}/", azureADOptions.TenantId); options.TokenValidationParameters.ValidateAudience = true; options.TokenValidationParameters.ValidateLifetime = true; options.TokenValidationParameters.ValidateIssuerSigningKey = true; options.TokenValidationParameters.ValidAudiences = new string[] { options.Audience, $"api://{options.Audience}" }; return services; }
In the above snippets, options.Authority refers to the issuer which issues the access token. For AAD v2.0 endpoint which is Microsoft Identity Platform endpoint, the authority url has the following format:
https://login.microsoftonline.com/{tenantId}/v2.0
The next few lines of codes configure what information we want to validate in the JWT token. With the above codes, I am able to validate:
Because the configurations can be lengthy depending on your implementation, it’s a good idea to organize them into an extension method, as in the above example snippets. In the startup class, you can simply call the extension method to configure the services.
Below shows the “AzureADApp” section in appsettings.
{
"AzureADApp": { "ClientId": "********-****-****-5678-e381a16a1765", "TenantId": "********-****-****-1234-2283395ed453", "Instance": "https://login.microsoftonline.com/" }
}
That’s it. Hopefully this post has given you some information to implement OAuth2 client credentials flow in your .NET core project, leveraging AAD, MSAL and .NET core security middlewares.
To learn more about JWT, check out my post.
Microsoft identity platform and the OAuth 2.0 client credentials flow
Overview of ASP.NET Core Authentication
A look behind the JWT bearer authentication middleware in ASP.NET Core
On The Nature of OAuth2’s Scopes
Quickstart: Register an application with the Microsoft identity platform
Building a fully multitenant system using Microsoft Identity Framework and SQL Row Level Security
Understanding Message Flow in Microsoft Teams Bot Development.
Migrating from Microsoft.AspNetCore.Authentication.AzureAD to Microsoft Identity Web authentication library to integrate with Azure AD.
How to authenticate user against Azure ADB2C from Angular app using oidc-client-js.
Using oidc-client-js to obtain tokens from Azure AD (v1.0) or Microsoft identity platform (v2.0) .
Building multitenant application – Part 2: Storing value into database session context from ASP.NET core web API
Building multitenant application – Part 1: Multitenant database using Row Level Security
Azure AD authentication in angular using MSAL angular v2 library