ASP.NET Core JwtBearer library: what’s new?

ASP.NET Core 6 – JwtBearer library: what’s new?

As a developer and trainer, it is hard to keep up with all the changes in all the libraries. In this blog post, I will summarize the recent key changes that I have found in the ASP.NET Core JwtBearer library versions 3.1.22, 5.0.13, and 6.0.1.

The JwtBearer library is distributed in the Microsoft.AspNetCore.Authentication.JwtBearer NuGet package. The corresponding source code can be found in the /src/Security/Authentication/ folder in the ASP.NET Core repository on GitHub. 

What is the purpose of the ASP.NET Core JwtBearer library?

The library is implemented as an ASP.NET Core request pipeline middleware, and its sole purpose is to authenticate incoming requests. It will look for JW bearer tokens in the request and, if a token is found, validate it and create a ClaimsPrincipal User instance. The authorization handler can then authorize the user to get access to the requested resource.


What runtime does each version target?

The different versions of the JwtBearer NuGet package target different runtimes:

  • Version 3.x.x targets .NET Core 3.1
  • Version 5.x.x targets .NET 5.x
  • Version 6.x.x targets .NET 6.x

That sounds obvious and logical, but the annoying thing with Visual Studio (as of today) is that, even if you have a .NET Core 3.1 application, it still tells you that version 6 is available, even though it should know that version 6.x.x is not compatible with your current project:


As a good developer, you want to keep your packages up to date, but trying to update it will result in the following error:

This sure is a bit annoying 🙄

Comparing the editions of JwtBearer

To do the research, I really like to get down to the true source, and the best source is of course the ASP.NET source code. So I downloaded it for the three different versions and then compared the result using Araxis merge. I could have done the same comparisons using NDepend or JustAssembly from Telerik, but I chose to use Araxis merge because it gave me the best overview for this blog post. Using Araxis merge I can compare all three versions at the same time as shown below:

The interesting source code for the authentication handlers in ASP.Net Core is found in the /src/Security/Authentication/ directory on GitHub.

What has changed between the revisions of JwtBearer?

There are of course plenty of small changes in this library, and many of the changes consist of added XML comments and improved support for non-nullable reference types. You can read my blog post about non-nullable reference types to learn more about this very cool language feature that was added in C# 8. 

But let’s look at the ones that actually matter for us using this library:

Map inbound claims option

This feature, which was introduced in version 5, allows us to very easily disable one of the most annoying features in the .NET authentication stack, and that is the automatic renaming of some of the claims found in the token.

The flag is by default set to true, but can now easily be disabled using:

services.AddAuthentication()
.AddJwtBearer(options =>
{
    options.MapInboundClaims = false;
    //...
});

Let’s compare the result when this flag is false or true. If we send the following access token in a request to the library:

{  
"nbf": 1642960856,  
"exp": 1642964456,  
"iss": "https://localhost:6001",  
"aud": "paymentapi",  
"client_id": "client",  
"managment": "yes",  
"email": "[email protected]",  
"name": "tore nestenius",  
"role": [ "admin", "developer", "support" ],
 "website": "https://www.tn-data.se",  
"jti": "9320D007CFA6EF1C21ACA09908216BC7",  
"iat": 1642960856,  
"scope": [ "payment" ]

Then the resulting ClaimsPrincipal User will contain the following set of claims:

MapInboundClaims = false
Claims - nbf=1642960856
 - exp=1642964456
 - iss=https://localhost:6001
 - aud=paymentapi
 - client_id=client - managment=yes
 - [email protected]
 - name=tore nestenius
 - role=admin
- role=developer
- role=support
- website=https://tn-data.se - iat=1642960856
- scope=payment

MapInboundClaims = true
Claims
-nbf=1642961012
-exp=1642964612
-iss=https://localhost:6001 - aud=paymentapi
-client_id=client
-managment=yes
-http://schemas.xmlsoap.org/ws/2005/05/identity/claims/[email protected]
-name=tore nestenius
-http://schemas.microsoft.com/ws/2008/06/identity/claims/role=admin
-http://schemas.microsoft.com/ws/2008/06/identity/claims/role=developer
-http://schemas.microsoft.com/ws/2008/06/identity/claims/role=support
-http://schemas.xmlsoap.org/ws/2005/05/identity/claims/webpage=https://tn-data.se
-iat=1642961012
-scope=payment

I have answered questions about claims mapping on Stack Overflow for quite some time now and I wish this flag was set to false by default. This would have made life so much easier for developers working with claims.

Customizing the JwtBearer back channel HttpClient

The JwtBearer will, via the Configuration Manager, make HTTP(s) requests to our identity provider through the back channel. For example, it will use this channel to download the openid-configuration and the public keys from the authorization provider (for example IdentityServer).

Before version 6, we could provide our own HttpMessageHandler to customize the BackChannel request and response, by for example:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.BackchannelHttpHandler = new MyBackChannelListener();
//...
};

 Why would you want to provide your own? Some of the use-cases can be:

  • Improve logging of the backchannel requests
  • Add custom headers to the outgoing request
  • Inspect the response
  • Implement automatic retry when a request fails, perhaps using the Polly library.

In version 6 they also introduced the option to provide your own HttpClient instance. This was added as a response to GitHub issue #27426.

This allows us to provide our own client like this:

builder.Services.AddAuthentication()
.AddJwtBearer(options =>
{    
options.Backchannel = new HttpClient()
    
{
        
//Custom client options
    
};
});

Why would you want to provide your own client?

  • Work with the HttpClientFactory
  • Better control of the HTTP protocol to be used
    (i.e., only use HTTP/2 or HTTP/1.1)

Some related resources

Customizing the configuration refresh intervals

In .NET 5 they introduced two new options to control the backchannel refresh intervals and you define them in code using:

services.AddAuthentication().AddJwtBearer(options =>
{
    //.NET 5 defaults    
options.AutomaticRefreshInterval = new TimeSpan(1, 0, 0, 0); //24 hours    
options.RefreshInterval = new TimeSpan(0, 0, 0, 30);  //30 seconds

 

   //.NET 6 defaults    
options.AutomaticRefreshInterval = new TimeSpan(0, 12, 0, 0); //12 hours   
options.RefreshInterval = new TimeSpan(0, 0, 5, 0);  //5 minutes
});

In .NET 6 they changed the default values as shown above, but the main question is of course, what do they control?

Automatic refresh interval

This parameter will control how often it will download and refresh the cached openid-configuration and the JWKS signing keys. 

Refresh interval

If a back channel request to the identity provider fails, then this value will control the wait time between retries. If it fails to refresh the configuration, then it will keep using the existing configuration that was retrieved earlier.

JwtBearer Dependencies

The JwtBearer also depends on a few other libraries, which are:

These three libraries are all located in a different repository named azure-activedirectory-identitymodel-extensions-for-dotnet. It is not located under the ASP.NET Core, instead it is located under the AzureAD  organization. 

The versioning of these packages does not follow the same versioning pattern as ASP.NET Core does, so in my investigation, I used the following versions:

  • ASP.NET Core 3.1 -> JwtBearer (3.1.22) -> …OpenIDConnect (5.5.0)
  • ASP.NET 5.0-> JwtBearer (5.0.13) -> …OpenIDConnect (6.7.1)
  • ASP.NET 6.0 -> JwtBearer (6.0.1) -> …OpenIDConnect (6.10.0)

Naturally there have been numerous improvements here too, but for those of us who are working with APIs, the TokenValidationParameters is the most interesting one for us.

services.AddAuthentication().AddJwtBearer(options =>
{    
//For example   
options.TokenValidationParameters.ValidateIssuer = false;
...
}

What interesting changes are here? If we compare version 5.5.0 with 6.10.0, we see that some of the most interesting changes are the following:

  • New validation delegates

    • AlgorithmValidator
      By providing a custom delegate to this parameter, you can do your own validation of the cryptographic algorithm used. If provided, then the valid algorithms provided in the ValidAlgorithms parameter will be ignored. Discussions about this delegate can be found here.
    • TypeValidator
      By providing a custom delegate to this parameter, you can do your own token type validation instead of the built-in default one. Discussions about this delegate can be found in Issue 1378 and Issue #1385
  • New properties

    • IgnoreTrailingSlashWhenValidatingAudience
      Setting this property will control if a ‘/’ is significant at the end of the audience or not. (default true)
    • TryAllIssuerSigningKeys
      Will control whether all the signing keys should be tried during signature validation when the referred key in the token is not found. (default true)
    • ValidAlgorithms
      Contains a list of the valid algorithms for cryptographic operations.
    • ValidTypes
      Contains a list of the valid token types.

Conclusion

Keeping up with all the changes in the .NET stack is hard and using Araxis merge allowed me to quickly compare three versions of the code base at the same time. The most annoying thing when dealing with the authentication stack is that the code involved is located in two different repositories and it is really annoying that the code in the AzureAD repository is not covered by https://source.dot.net/, which otherwise is very handy when searching the Microsoft code base. 

When I create my training materials I often do dive deep into the .NET source code, and looking under the hood gives you another perspective on how things actually work and the amazing work the .NET developers put into the stack!

While browsing the source code I found some inaccuracies in the code XML comments, so I filed an issue to the ASP.NET Core team on GitHub here and it was quickly resolved within a few days. 

I have built training courses about this topic and it has helped several companies move forward with their journey. Check out the courses here.

 

About the author

Tore Nestenius has been interested in computers since he got his first Commodore VIC-20. Today he works as a freelance developer and trainer with a focus on ASP.NET Core, IdentityServer, OpenId Connect and web security. Read more about him here and about his services and training

 

Other posts by me:

Tore’s Newsletter

Be the First to Know! Get notified about my latest blog posts, upcoming presentations, webinars, and more — subscribe today!

Cartoon of Tore Nestenius
Share This Story

Related Posts

About Tore Nestenius - Freelance software development instructor and consultant.

About me

My name is Tore Nestenius and I’m a trainer and senior software developer focusing on Architecture, Security and Identity, .NET, C#, Backend, and Cloud, among other things.

Do You Want Tore To Be Your Mentor?

Categories