JWT Validation Middleware in ASP.NET Core

HTTP 440 ( Login Time-out) is a good and meaningful HTTP response code. Despite of that being unofficial and used only by a few Microsoft services – I found it very handy and meaningful to indicate if the JWT token is expired, rather than showing a 401 (Unauthorised) which is broad. In my opinion, HTTP 440 gives a good developer experience (and perhaps user experience as well) where you are the developer of an API and keeping a log of errors (eg: Azure Application insights).

Since most web applications are using short living JWT token as a mechanism for authentication, logging 401s might give a totally wrong impression. Additionally if the HTTP response code is 440 instead of 401, a simple refreshing mechanism will be enough rather than a full inspection of the JWT token to understand the problem if the response code is the latter.

An ASP.NET Core middleware to check the time validity of the JWT is useful to short-circuit the response by implementing circuit breaker pattern and return a response very quickly, rather than using Filters as before reaching the filter pipeline, the request goes through the middleware pipeline.

Traversal of a REST API Request

The below is how I have recently implemented a custom middleware to check the JWT token expiration and short-circuit the request. This implementation uses the System.IdentityModel.Tokens.Jwt nuget package (it uses version 6.11.1 at the time of writing this post).

Custom Middleware: JwtTimeValidator

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace CustomMiddleware.Test.CustomMiddleware
{
    public class JwtTimeValidator
    {
        private readonly RequestDelegate _next;

        public JwtTimeValidator(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            var authorizationHeader = context.Request.Headers["Authorization"].ToString();
            if(!string.IsNullOrEmpty(authorizationHeader))
            {
                // Validation of the Token expiration
                var jwtEncodedString = authorizationHeader[7..];
                var token = new JwtSecurityToken(jwtEncodedString: jwtEncodedString);
                if (DateTime.Compare(DateTime.UtcNow, token.ValidTo) > 0)
                {
                    context.Response.Clear();
                    context.Response.StatusCode = 440;
                    await context.Response.WriteAsync("Login Time-out");
                }
                await _next(context);
            }
            else
            {
                context.Response.Clear();
                context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                await context.Response.WriteAsync("Unauthorized");
            }
        }
    }
}

Middleware Extensions: CustomMiddlewareExtensions

using Microsoft.AspNetCore.Builder;

namespace CustomMiddleware.Test.CustomMiddleware
{
    public static class CustomMiddlewareExtensions
    {
        public static IApplicationBuilder UseJwtTimeValidator(
            this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<JwtTimeValidator>();
        }
    }
}

Startup.cs

using CustomMiddleware.Test.CustomMiddleware;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace CustomMiddleware.Test
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            // Custom middleware
            app.UseJwtTimeValidator();

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

Such implementation is helpful if the API you’re is depending on famous APIs like Azure REST API, Microsoft Graph API, etc because with this implementation in place, you may simply ask the user to refresh the session instead of passing an expired JWT token the dependancy API and wait for that API to send a 401 response.

On the usage of 440, if you ask me, if one should choose “meaningfulness” over “standard” – it depends – it is the decision of you and your team. Contrariety, I am a firm believer of using the right standards is the way to make software maintainable.

Happy Coding!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s