I recently had a chance to clean up some of the deprecated libraries I used for validating a JWT access token or obtain one via the client-credentials flow or the on-behalf-of flow. The libraries I used were under the Microsoft.AspNetCore.Authentication.AzureAD packages. Per the document, since ASP.NET core 5.0, users should use the Microsoft.Identity.Web package to integrate with Azure AD and Azure ADB2C.
Before, to validate a token, I used the Microsoft.AspNetCore.Authentication.AzureAD package along with the Microsoft.AspNetCore.Authentication.JwtBearer package. I explicitly specified the token validation parameters to validate a token.
public static IServiceCollection AddSecurity(this IServiceCollection services, IConfiguration configuration) { var aadOptionSection = configuration.GetSection("WebAPIToSecure"); AzureADOptions aadOptions = aadOptionSection.Get<AzureADOptions>(); services.AddCache(); services.AddSession(); services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme) .AddAzureADBearer(options => { aadOptionSection.Bind(options); }); services.Configure<AzureADOptions>(aadOptionSection); services.Configure<ConfidentialClientApplicationOptions>(aadOptionSection); services.AddScoped<ITokenAcquisition, TokenAcquisition>(); 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}/", aadOptions.TenantId); options.TokenValidationParameters.ValidateIssuer = true; options.TokenValidationParameters.ValidateAudience = true; options.TokenValidationParameters.ValidateLifetime = true; options.TokenValidationParameters.ValidAudiences = new string[] { options.Audience, $"api://{options.Audience}" }; options.Events = new JwtBearerEvents(); options.Events = JwtBearerMiddlewareDiagnostics.Subscribe(options.Events); }); return services; }
When switching to the Microsoft.Identity.Web package, I learned that the library performs much of the validation automatically by default. Per the document, the library checks for the following:
– Audience: The token is targeted for the web API.
– Sub: It was issued for an app that’s allowed to call the web API.
– Issuer: It was issued by a trusted security token service (STS).
– Expiry: Its lifetime is in range.
– Signature: It wasn’t tampered with.
Token Validation
As such, I was able to remove much of the configs, as demonstrated in the below code snippet.
public static IServiceCollection AddSecurity(this IServiceCollection services, IConfiguration configuration) { services.Configure<ConfidentialClientApplicationOptions>(aadOptionSection); services.AddScoped<ITokenAcquisition, TokenAcquisition>(); services.AddMicrosoftIdentityWebApiAuthentication(configuration, configSectionName: "WebAPIToSecure"); return services; }
Note that you still can customize the token validation parameters if necessary, as per the document.
The web API in turn needs to access another downstream web API, which also requires an access token. Previously, I explicitly setup ConfidentialClientApplicationOptions and ITokenAcquisition. However, with Microsoft.Identity.Web package, the setup is more fluent, as shown in the below code snippet.
services.AddMicrosoftIdentityWebApiAuthentication(_configuration, configSectionName: "WebAPIToSecure") .EnableTokenAcquisitionToCallDownstreamApi() .AddDownstreamWebApi("DownstreamAPI", _configuration.GetSection(DownstreamAPI)) .AddInMemoryTokenCaches();
Below code snippet shows the relevant configs in app settings.
"WebAPIToSecure": { "Instance": "https://login.microsoftonline.com/", "ClientId": "********-****-****-****-************", "ClientSecret": "secret", "TenantId": "********-****-****-****-************" } "DownstreamAPI": { "BaseUrl": "https://localhost:44307/api", "ClientId": "********-****-****-****-************", }
Note that you don’t even need to specify configSectionName if you name the section as “AzureAD” in app settings, as that is the default. In my project, I named the section differently, so I specified the parameter.
Below code snippets show how I use the ITokenAcquisition to obtain an access token via the on-behalf-of flow for the web API (WebAPIToSecure) to access the downstream API (DownstreamAPI).
var downstreamAPIClientId = configuration.GetSection("DownstreamAPI:ClientId").Get < string > (); var authenticationResult = await tokenAcquisition.GetAuthenticationResultForUserAsync(new string[] { $ "api://{downstreamAPIClientId}/.default" }); var accessToken = authenticationResult.AccessToken;
In the above snippets, I call the method GetAuthenticationResultForUserAsync as it is the one for the on-behalf-of flow. The ITokenAcquisition interface also exposes the other method, GetAccessTokenForAppAsync, which is for the client-credentials flow. To learn more, check out the document.
Authentication: AzureAD.UI and AzureADB2C.UI APIs and packages marked obsolete
Building a fully multitenant system using Microsoft Identity Framework and SQL Row Level Security
How to authenticate user against Azure ADB2C from Angular app using oidc-client-js.
Implement OAuth2 Client-Credentials flow with Azure AD and Microsoft Identity Platform.
Using oidc-client-js to obtain tokens from Azure AD (v1.0) or Microsoft identity platform (v2.0) .
Web scraping in C# using HtmlAgilityPack
Building multitenant application – Part 2: Storing value into database session context from ASP.NET core web API
Common frameworks, libraries and design patterns I use
Build and deploy a WebJob alongside web app using azure pipelines