Custom Authentication (Validate JWT Token) In .NET Core

Introduction

Hello Friends!!!! In this article, I am going to explain how to implement the custom authentication handler and how to inject it as middleware in the .NET Core. With the custom authentication handler, we are going to validate the JWT(JSON Web Token) token from the request (Authorization header). 

JWT Token Validation

  1. Create Custom Authentication handler to validate JWT token
  2. Get the metadata from the custom authorization server (OAuth)
  3. Inject the Authentication handler in the middleware

How custom authentication works

Create a Custom Authentication handler

Step 1

Create .NET Core Project.

Step 2

Create "AuthConfigManager" class. It is used to get the metadata from the authorization server.

Step 3

Add below references to the project using the NuGet package manager.

  • Microsoft.IdentityModel.Protocols
  • Microsoft.IdentityModel.Protocols.OpenIdConnect

Step 4

Add below code in the "AuthConfigManager" class. Here, we are getting the metadata from the authorization server. Because, instead of keeping the sign key in the local configuration, will get from the authorization server using metadata endpoint. The following endpoints return OpenID Connect or OAuth 2.0 metadata related to your Org Authorization Server. 

OAuth - https://${yourAuthServer}/.well-known/oauth-authorization-server

public static class AuthConfigManager {
    private static IConfigurationManager < OpenIdConnectConfiguration > configManager;
    private static IConfigurationManager < OpenIdConnectConfiguration > GetConfigurationManager(string metadataAddress) {
        if (configManager == null) {
            return new ConfigurationManager < OpenIdConnectConfiguration > (metadataAddress, new OpenIdConnectConfigurationRetriever(), new HttpDocumentRetriever());
        }
        return configManager;
    }
    public static OpenIdConnectConfiguration GetMetaData(string metadataAddress) {
        var configManager = GetConfigurationManager(metadataAddress);
        var metaData = configManager.GetConfigurationAsync(
            default).Result;
        return metaData;
    }
}

Step 5

Create the "CustomAuthHandler" class. It is used to validate the JWT token against the "TokenValidationParameters".

Step 6

Add the below code in the "CustomAuthHandler" class, to get the metadata from "AuthConfigManager".

public bool IsValidToken(string jwtToken, string issuer, string audience, string metadataAddress) {
    var openIdConnectConfig = AuthConfigManager.GetMetaData(metadataAddress);
    var signingKeys = openIdConnectConfig.SigningKeys;
    return ValidateToken(jwtToken, issuer, audience, signingKeys);
}

Step 7

Add the below code in "CustomAuthHandler" to validate the JWT token. If any exception occurs, then throw that exception.

private bool ValidateToken(string jwtToken, string issuer, string audience, ICollection < SecurityKey > signingKeys) {
    try {
        var validationParameters = new TokenValidationParameters {
            RequireExpirationTime = true,
                ValidateLifetime = true,
                ClockSkew = TimeSpan.FromMinutes(1),
                RequireSignedTokens = true,
                ValidateIssuerSigningKey = true,
                IssuerSigningKeys = signingKeys,
                ValidateIssuer = true,
                ValidIssuer = issuer,
                ValidateAudience = true,
                ValidAudience = audience
        };
        ISecurityTokenValidator tokenValidator = new JwtSecurityTokenHandler();
        var claim = tokenValidator.ValidateToken(jwtToken, validationParameters, out
            var _);
        var scope = claim.FindFirst(c => c.Type.ToLower() == "<Here Scope Type>" && (c.Value.ToLower() == "<Here Scope>"));
        if (scope == null) throw new Exception("404 - Authorization failed - Invalid Scope");
        return true;
    } catch (Exception ex) {
        throw new Exception("404 - Authorization failed", ex);
    }
}

In the above method, validating the below parameters in the JWT token,

  • RequireExpirationTime =  It indicating whether tokens must have an 'expiration' value.
  • ValidateLifetime = Check if the token has expired or not
  • ClockSkew = Clock skew to apply when validating a time
  • RequireSignedTokens = It is indicating whether a SecurityToken can be considered valid if not signed.
  • ValidateIssuerSigningKey = Validate signature of the token 
  • IssuerSigningKeys = Validate signature of the token
  • ValidateIssuer = Validate the server where generates the token
  • ValidIssuer = It is the issuer value that will be used to check against the token's issuer
  • ValidateAudience = Validate the recipient of the token
  • ValidAudience = It is audience value that will be used to check against the token's audience

Step 8

The value of the issuer, the audience are stored in the appsettings.json file as below,

{
  "JwtToken": {
    "Issuer": "TestIssuer.com",
    "Audience": "Test"
  }
}

Step 9

Create the "AuthMiddleware" class and add the below code to invoke the JWT validation. In the below code, just extract the JWT token from the Authorization header in the request and call the "IsValidToken()" function to validate the JWT token. If any exception occurs then send the "Unauthorized" message and "401" status code to the user.

public class AuthMiddleware {
    private readonly RequestDelegate next;
    private readonly IConfiguration configuration;
    public AuthMiddleware(IConfiguration appConfigurationr) {
        configuration = appConfigurationr;
    }
    public async Task Invoke(HttpContext httpContext) {
        try {
            var path = httpContext.Request.Path;
            string token = string.Empty;
            string issuer = configuration["JwtToken:Issuer"]; //Get issuer value from your configuration
            string audience = configuration["JwtToken:Audience"]; //Get audience value from your configuration
            string metaDataAddress = issuer + "/.well-known/oauth-authorization-server";
            CustomAuthHandler authHandler = new CustomAuthHandler();
            var header = httpContext.Request.Headers["Authorization"];
            if (header.Count == 0) throw new Exception("Authorization header is empty");
            string[] tokenValue = Convert.ToString(header).Trim().Split(" ");
            if (tokenValue.Length > 1) token = tokenValue[1];
            else throw new Exception("Authorization token is empty");
            if (authHandler.IsValidToken(token, issuer, audience, metaDataAddress)) await next(httpContext);
        } catch (Exception) {
            httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
            HttpResponseWritingExtensions.WriteAsync(httpContext.Response, "{\"message\": \"Unauthorized\"}").Wait();
        }
    }
}

Step 10

Configure the "AuthMiddleware" in the "Configure" method in the "Startup" class.

Whenever the API is getting called, the request is passed to "AuthMiddleware" and it will validate the JWT token (Authorization header in the request) before going to the corresponding controller.

The use of building the custom Authentication is, we can validate the JWT token based on our use case and also customize the validation part.

Hope you liked it. If you have any doubts or comments about this, please let me know in the comments.