Published: January 19 2022

.NET 6.0 - Create and Validate JWT Tokens + Use Custom JWT Middleware

Tutorial built with .NET 6.0

Other versions available:

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

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by