.NET 6.0 - Create and Validate JWT Tokens + Use Custom JWT Middleware
Tutorial built with .NET 6.0
Other versions available:
- .NET: .NET 5.0, ASP.NET Core 3.1
This is a quick example of how to create and validate JWT tokens in .NET 6.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 6.0 - User Registration and Login Tutorial with Example API.
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 6.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 6.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
object used on line 17
contains the above methods for validating and generating JWT tokens, the full JwtUtils
class and IJwtUtils
interface are included below for completeness.
namespace WebApi.Authorization;
using WebApi.Services;
public class JwtMiddleware
{
private readonly RequestDelegate _next;
public JwtMiddleware(RequestDelegate next)
{
_next = next;
}
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.
namespace WebApi.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using WebApi.Entities;
[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.
namespace WebApi.Authorization;
[AttributeUsage(AttributeTargets.Method)]
public class AllowAnonymousAttribute : Attribute
{ }
Custom .NET JWT Utils Class
Below 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.
namespace WebApi.Authorization;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using WebApi.Entities;
using WebApi.Helpers;
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!