JSON Web Tokens (JWTs) have become very popular. Many developers use them in their applications for authentication and authorization purposes, but not all of them really know why and how to validate their JWTs.
This article will explain why you need to validate a JWT, what it means in general, and give you an overview of the many ways you can do it in the .NET platform.
What Are JWTs?
If you landed on this article, you probably know what a JSON Web Token is. If not, here is a great introduction to JWTs and a tool to decode them.
Simply put, JSON Web Token is an open standard that defines how to share information between parties using the JSON format. The JWT structure consists of three concatenated Base64 url-encoded strings, separated by dots: the header, the payload, and the signature The information carried by a JWT is digitally signed so you can verify its origin and trust it. The digital signature also guarantees the integrity of the information, but it doesn't guarantee privacy unless explicitly encrypted.
Besides this, the JWT specification doesn't define the specific purpose of a token. While most developers think of authentication when dealing with JWTs, this is not an intrinsic property of tokens in the JWT format. JWT is just a format. The meaning of the token depends on the specific context or application.
For example, in the OpenID Connect (OIDC) context, the JWT format is used for the ID token, which is the token that proves a user is authenticated. In the OAuth 2.0 context, the JWT format can optionally be used for the access token, which is the token that allows a client application to access a specific resource on behalf of the user.
To learn more about ID and access tokens and their differences, you can read this blog post or watch this video instead.
Interested in getting up-to-speed with JWTs as soon as possible?
Download the free ebookWhat Does Token Validation Mean?
The main purpose of a JWT is to carry information that the recipient can trust. So, the first thing your application needs to do when it receives a JWT is to ensure that the information it carries is trustworthy. This is where validation comes in.
In short, validating a JSON Web Token means ensuring that the token has the structure defined in the standard specification and that you can trust the information it carries.
The JWT validation is based on the following five criteria:
Token structure. The first check is about the token's structure. Does the token match the structure of a JSON Web Token? If the token doesn't follow the standard guidelines, it's not valid.
- Token integrity. The next check is for the token's integrity. Has the token been tampered with? You can verify this by checking the signature. If you verify that the token's signature is valid, then it has not been altered in any way.
- Token expiration. JWTs have an expiration time defined in the
claim. Is the token expired when it has been received? If a token has expired, you should no longer trust its contents.exp
- Expected authority. The next check is to verify that the token was issued and signed by the expected sender (authority). There are two steps to this check:
- Verify that the authority is the one expected by your application by comparing it to the
claim (issuer).iss
- Verify that the key used to sign the JWT actually belongs to the expected authority.
- Verify that the authority is the one expected by your application by comparing it to the
- Expected audience. Finally, verify that the token is intended for your application. In other words, you should check that the
claim matches with the value that identifies your application. For example, in an ID token, the value for the audience is the client ID; in an access token in JWT format, the value for the audience is the API identifier.aud
To learn more about digital signatures, read this article. Check out this article for the differences between the signing algorithms used in a JWT.
Token validation doesn't involve the custom information carried by the token. This is an application-specific logic, which is out of the scope of token validation. For example, validating an ID token does not include verifying that the user's email address in the
email
claim exists.Validate a JWT Using an Auth0 SDK
Now that you know what validating a JWT means, you are ready to learn how to validate your tokens in .NET.
Before jumping into the validation code, you should evaluate whether you really need to explicitly validate the JWTs you receive from an issuer like Auth0. Depending on the specific application, you may want to use an Auth0 SDK, which will take care of all the validation steps for you. For example, if you are developing an ASP.NET Core MVC application or a Blazor Server application, you can use the Auth0 ASP.NET Core Authentication SDK. This SDK will take care of the entire process of obtaining the JWT tokens for your application, including token validation. Check out this article for an overview of this specific SDK.
So, if your application can benefit from an SDK, the best move is to write less code and get the JWT validation for free. Take a look at this article to learn which Auth0 SDK is right for your .NET application type.
Using the ASP.NET Core Middleware
If, for some reason, you can't use an Auth0 SDK, you can configure your ASP.NET Core application by using two middleware:
- The OpenIdConnect middleware, which allows your ASP.NET Core web applications to authenticate users and get ID and access tokens via OpenId Connect.
- The JwtBearer middleware, which enables your ASP.NET Core web API to handle access tokens.
Both middleware provide you with a convenient way to obtain and validate JWT tokens. The following code snippet shows an example of how to configure OpenIdConnect middleware:
// Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect("Auth0", options => { options.Authority = $"https://{builder.Configuration["Auth0:Domain"]}"; options.ClientId = builder.Configuration["Auth0:ClientId"]; options.CallbackPath = new PathString("/callback"); }); // ...other code...
The next example shows how to use the JwtBearer middleware:
// Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { options.Authority = $"https://{builder.Configuration["Auth0:Domain"]}"; options.Audience = builder.Configuration["Auth0:Audience"]; }); // ...other code...
In both cases, by configuring the
Authority
option, you implicitly ask the middleware to validate your tokens. The middleware will use this value to retrieve the issuer's signing key, which is needed to validate the token's signature. All the other required validation steps are performed automatically. There is nothing else you need to do.Simplified configuration for the JwtBearer middleware
Starting with .NET 7, you can use the JwtBearer middleware in a simplified way. You define your validation values in the
appsettings.json
file, as in the following example:// appsettings.json { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "Authentication": { "Schemes": { "Bearer": { "Authority": "https://{DOMAIN}", "ValidAudiences": [ "{AUDIENCE}" ], "ValidIssuer": "{DOMAIN}" } } } }
Then you can simply add the middleware to your ASP.NET Core application, as follows:
// Program.cs // ...existing code... var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication().AddJwtBearer(); // ...existing code...
The behavior is exactly the same as before but with much less code.
Custom validation with the ASP.NET Core middleware
If you have specific needs, you can customize the JWT validation using the
TokenValidationParameters
option. For example, if you already have the issuer's public key stored on your file system, you can configure your middleware as shown in the following example:// Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { options.Audience = builder.Configuration["Auth0:Audience"]; options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuer = true, ValidIssuer = $"{builder.Configuration["Auth0:Domain"]}", ValidateIssuerSigningKey = true, IssuerSigningKey = new X509SecurityKey(new X509Certificate2(certificateFilePath)), ValidateLifetime = true }; }); // ...other code...
You can see that the
Audience
option is still there while the Authority
option is missing. Instead of the Authority
option, you find the TokenValidationParameters
option, which is assigned an instance of the TokenValidationParameters
class. The properties of this instance define what to check in order to complete the JWT validation. Specifically, you ask to validate the issuer (ValidateIssuer
) and provide the value to match with (ValidIssuer
). You ask to validate the issuer's signature (ValidateIssuerSigningKey
) and provide the issuer's public signing key (IssuerSigningKey
) embedded in a certificate stored in the file system. And finally, you ask to check the expiration time (ValidateLifetime
).You can see how removing the
Authority
option from the middleware configuration forces you to manually specify what to check in order to validate the token.Validate a JWT “By Hand”
In case you can't use the ASP.NET Core middleware, for example, when you are not developing a web application, don't get desperate. .NET gives you what you need, although this requires some effort on your side.
Validate your token
First, you need to install the
Microsoft.IdentityModel.Tokens
package from NuGet:dotnet add package Microsoft.IdentityModel.Tokens
This package provides everything you need for token manipulation, including validation, of course.
Have a look at the following function:
public bool ValidateToken( string token, string issuer, string audience, ICollection<SecurityKey> signingKeys, out JwtSecurityToken jwt ) { var validationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = issuer, ValidateAudience = true, ValidAudience = audience ValidateIssuerSigningKey = true, IssuerSigningKeys = signingKeys, ValidateLifetime = true }; try { var tokenHandler = new JwtSecurityTokenHandler(); tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken); jwt = (JwtSecurityToken)validatedToken; return true; } catch (SecurityTokenValidationException ex) { // Log the reason why the token is not valid return false } }
This function takes a token and validates it by using the other parameters: the issuer, the audience, and the signing keys. If the token is valid, it returns
true
and provides a JwtSecurityToken
instance representing the token as the jwt
output parameter.Once again, you see the
TokenValidationParameters
class, which allows you to define the validation criteria. This time there is no implicit validation. You need to specify all the required validation criteria and the valid values. Then you create a token handler (JwtSecurityTokenHandler
) and use it to validate the token against the validation parameters. If the validation succeeds, you get an object representing the token, which you assign to the output parameter after casting to JwtSecurityToken
. Finally, the function returns true
.If the validation fails, it should log the reason for the failed validation and return
false
.Get the signing keys
The
ValidateToken()
function expects you to pass the basic information for the token validation: the token issuer identifier, the audience, and the issuer's signing keys. You may have all this information on hand or need to retrieve some of it. For example, you can obtain the signing key of the issuer online. According to the OIDC standard, you can find the set of keys (JSON Web Key Sets) to verify the issuer's signatures at a public and well-defined URL: https://{DOMAIN}/.well-known/openid-configuration
.Read this document to learn more about the JSON Web Key Sets.
To validate a JWT using the
ValidateToken()
function, you must first retrieve the signing key from this URL. As you can see, things start to get complex. This is why I suggest using an Auth0 SDK in the first place, or at least the ASP.NET Core middleware, if possible.However, if using one of these tools is not feasible in your specific case, here is what you need to do to get the issuer's signing keys.
First, you need to install another package from NuGet: the
Microsoft.IdentityModel.Protocols.OpenIdConnect
package:dotnet add package Microsoft.IdentityModel.Protocols.OpenIdConnect
This package provides you with the
ConfigurationManager
class, which helps you retrieve the JSON Web Key Sets. The following code shows you how to obtain the signing key of your JWT issuer:var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>( $"https://{builder.Configuration["Auth0:Domain"]/.well-known/openid-configuration}", new OpenIdConnectConfigurationRetriever(), new HttpDocumentRetriever()); var discoveryDocument = await configurationManager.GetConfigurationAsync(); var signingKeys = discoveryDocument.SigningKeys;
Once you have the signing keys, you are ready to use the
ValidateToken()
function.Recap
Throughout this article, you have learned what JWT validation is and why you need to do it. You also learned that as a .NET developer, you have many options for validating the JWTs your application receives: from a zero-code approach to an almost manual validation.
With the Auth0 SDKs, you get the JWT validation for free. No specific code is required. The SDKs validate tokens for you.
Using the ASP.NET Core middleware, OpenIdConnect and JwtBearer, you get implicit token validation in the default cases. However, you can customize your validation criteria if you are dealing with special cases.
In case you need full control over the validation process, you can rely on the
Microsoft.IdentityModel.Tokens
package for the JWT manipulation and validation and on the Microsoft.IdentityModel.Protocols.OpenIdConnect
package for retrieving the issuer's signing keys.Of course, the suggestion is to delegate as much as possible to the SDKs and middleware because the less code you write, the fewer bugs you may create 🙂.
About the author
Andrea Chiarelli
Principal Developer Advocate
I have over 20 years of experience as a software engineer and technical author. Throughout my career, I've used several programming languages and technologies for the projects I was involved in, ranging from C# to JavaScript, ASP.NET to Node.js, Angular to React, SOAP to REST APIs, etc.
In the last few years, I've been focusing on simplifying the developer experience with Identity and related topics, especially in the .NET ecosystem.