Published:

ASP.NET Core 3.1 - Create and Validate JWT Tokens + Use Custom JWT Middleware

This is a quick example of how to create and validate JWT tokens in ASP.NET Core 3.1 using the JwtSecurityTokenHandler class which is part of the System.IdentityModel.Tokens.Jwt NuGet package. We also cover how to implement custom JWT authentication with 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, no other parts of the ASP.NET Core Identity system are used.


Installing the JWT Token Library via NuGet

.NET Core CLI: dotnet add package System.IdentityModel.Tokens.Jwt

Visual Studio Package Manager Console: System.IdentityModel.Tokens.Jwt


Creating a JWT Token in ASP.NET Core

This code generates a JWT token with the specified accountId as the "id" claim, meaning the token payload will contain the property "id": <accountId> (e.g. "id": 123).

public string GenerateJwtToken(int accountId)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes("[SECRET USED TO SIGN AND VERIFY JWT TOKENS, IT CAN BE ANY STRING]");
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new[] { new Claim("id", accountId.ToString()) }),
        Expires = DateTime.UtcNow.AddDays(7),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
    };
    var token = tokenHandler.CreateToken(tokenDescriptor);
    return tokenHandler.WriteToken(token);
}


Validating a JWT Token in ASP.NET Core

This code attempts to validate the provided JWT token and return the accountId from the token claims. If validation fails null is returned.

public int? ValidateJwtToken(string token)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes("[SECRET USED TO SIGN AND VERIFY JWT TOKENS, IT CAN BE ANY STRING]");
    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 accountId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);

        // return account id from JWT token if validation successful
        return accountId;
    }
    catch
    {
        // return null if validation fails
        return null;
    }
}


Validating Tokens in Custom JWT Middleware

Below is custom JWT middleware that validates the JWT token in the request "Authorization" header if it exists.

On successful JWT validation the middleware retrieves the associated account from the database and assigns it to context.Items["Account"] which makes the current account available to any other code running within the current request scope, which we'll use below in a custom authorize attribute.

The custom JWT middleware is from a boilerplate api project I posted recently, for more info and to test it out in a fully functioning project see ASP.NET Core 3.1 - Boilerplate API with Email Sign Up, Verification, Authentication & Forgot Password.

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WebApi.Helpers;

namespace WebApi.Middleware
{
    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, DataContext dataContext)
        {
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();

            if (token != null)
                await attachAccountToContext(context, dataContext, token);

            await _next(context);
        }

        private async Task attachAccountToContext(HttpContext context, DataContext dataContext, string token)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
                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 accountId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);

                // attach account to context on successful jwt validation
                context.Items["Account"] = await dataContext.Accounts.FindAsync(accountId);
            }
            catch 
            {
                // do nothing if jwt validation fails
                // account is not attached to context so request won't have access to secure routes
            }
        }
    }
}


Checking Token Validated with Custom Authorize Attribute

Below is a simple custom authorize attribute that checks if the above custom JWT middleware succeeded by checking if an account object was attached to the current http context for the request (context.HttpContext.Items["Account"]).

The custom authorize attribute is a slightly simplified version from a boilerplate api project I posted recently, for more info and to test it out in a fully functioning project see ASP.NET Core 3.1 - Boilerplate API with Email Sign Up, Verification, Authentication & Forgot Password.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using WebApi.Entities;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var account = (Account)context.HttpContext.Items["Account"];
        if (account == null)
        {
            // not logged in
            context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
        }
    }
}

 

Subscribe or Follow Me For Updates

Subscribe to my YouTube channel or follow me on Twitter or GitHub to be notified when I post new content.

 


Supported by