.NET 5.0 - Create and Validate JWT Tokens + Use Custom JWT Middleware
Built with .NET 5.0
Other versions available:
- .NET: .NET 6.0, ASP.NET Core 3.1
This is a quick example of how to create and validate JWT tokens in .NET 5.0 using the JwtSecurityTokenHandler
class which is part of the System.IdentityModel.Tokens.Jwt
NuGet package. We'll also cover how to implement custom JWT authentication using custom JWT middleware and a custom authorize attribute.
The code samples use the JWT token handler and a few related classes to create and validate JWT tokens, but no other parts of the .NET Identity system are used.
All of the code in this tutorial is taken from a .NET API tutorial I posted recently, for more info or to download and test the API locally see .NET 5.0 - Simple API for Authentication, Registration and User Management.
Installing the JWT Token Library via NuGet
.NET CLI: dotnet add package System.IdentityModel.Tokens.Jwt
Visual Studio Package Manager Console: System.IdentityModel.Tokens.Jwt
Create a JWT Token in .NET 5.0
This code generates a JWT token with the specified user.Id
as the "id"
claim, meaning the token payload will contain the property "id": <user.Id>
(e.g. "id": 123
).
The _appSettings.Secret
parameter on line 5
is a secret string used to sign and verify JWT tokens in the application, it can be any string.
public string GenerateToken(User user)
{
// generate token that is valid for 7 days
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
Validate a JWT Token in .NET 5.0
This code attempts to validate the provided JWT token
and return the userId
from the token claims. If the token is null or validation fails null
is returned.
public int? ValidateToken(string token)
{
if (token == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
// return user id from JWT token if validation successful
return userId;
}
catch
{
// return null if validation fails
return null;
}
}
Validate Tokens in Custom JWT Middleware
Below is custom JWT middleware that validates the JWT token contained in the request "Authorization"
header (if it exists).
On successful JWT validation the middleware retrieves the associated user
from the database and assigns it to context.Items["User"]
which makes the user object available to any other code running within the current request scope. The user object is then used by the custom Authorize
attribute below to perform authorization.
The JwtUtils
class used on line 24
contains the above methods for validating and generating JWT tokens, the full class is included below for completeness.
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System.Linq;
using System.Threading.Tasks;
using WebApi.Helpers;
using WebApi.Services;
namespace WebApi.Authorization
{
public class JwtMiddleware
{
private readonly RequestDelegate _next;
private readonly AppSettings _appSettings;
public JwtMiddleware(RequestDelegate next, IOptions<AppSettings> appSettings)
{
_next = next;
_appSettings = appSettings.Value;
}
public async Task Invoke(HttpContext context, IUserService userService, IJwtUtils jwtUtils)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
var userId = jwtUtils.ValidateToken(token);
if (userId != null)
{
// attach user to context on successful jwt validation
context.Items["User"] = userService.GetById(userId.Value);
}
await _next(context);
}
}
}
Check Token Validated with Custom Authorize Attribute
The custom [Authorize]
attribute is used to restrict access to controllers or specified action methods. Only authorized requests are allowed to access action methods that are decorated with the [Authorize]
attribute.
When a controller is decorated with the [Authorize]
attribute all action methods are restricted to authorized requests, except for methods decorated with the custom [AllowAnonymous]
attribute above.
Authorization is performed by the OnAuthorization
method which checks if there is an authenticated user attached to the current request (context.HttpContext.Items["User"]
). An authenticated user is attached by the above custom JWT middleware if the request contains a valid JWT token.
On successful authorization no action is taken and the request is passed through to the controller action method, if authorization fails a 401 Unauthorized
response is returned.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Linq;
using WebApi.Entities;
namespace WebApi.Authorization
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
// skip authorization if action is decorated with [AllowAnonymous] attribute
var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any();
if (allowAnonymous)
return;
// authorization
var user = (User)context.HttpContext.Items["User"];
if (user == null)
context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
}
}
}
Custom Allow Anonymous Attribute
The custom [AllowAnonymous]
attribute is used to allow anonymous access to specified action methods of controllers that are decorated with the [Authorize]
attribute. The custom authorize attribute above skips authorization if the action method is decorated with [AllowAnonymous]
.
I created a custom allow anonymous (instead of using the built-in one) for consistency and to avoid ambiguous reference errors between namespaces.
using System;
namespace WebApi.Authorization
{
[AttributeUsage(AttributeTargets.Method)]
public class AllowAnonymousAttribute : Attribute
{ }
}
Custom JWT Utils Class
This is the complete JWTUtils
class that contains the GenerateToken
and ValidateToken
methods covered at the start of the post. It is used by the custom JWT middleware above to validate tokens.
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using WebApi.Entities;
using WebApi.Helpers;
namespace WebApi.Authorization
{
public interface IJwtUtils
{
public string GenerateToken(User user);
public int? ValidateToken(string token);
}
public class JwtUtils : IJwtUtils
{
private readonly AppSettings _appSettings;
public JwtUtils(IOptions<AppSettings> appSettings)
{
_appSettings = appSettings.Value;
}
public string GenerateToken(User user)
{
// generate token that is valid for 7 days
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
public int? ValidateToken(string token)
{
if (token == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
// return user id from JWT token if validation successful
return userId;
}
catch
{
// return null if validation fails
return null;
}
}
}
}
Need Some .NET Help?
Search fiverr for freelance .NET developers.
Follow me for updates
When I'm not coding...
Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!