Custom JWT Handler In ASP.NET Core 7 Web API
Tags: webdev, programming, productivity, aspdotnet
Published: 2023-05-25 18:05:37

In ASP.NET Core Web API, you can create a custom JWT (JSON Web Token) handler to handle token authentication and authorization. The custom handler lets you define your logic for validating and processing JWT tokens.
Here's an example of how you can create a custom JWT handler in ASP.NET Core Web API:
### Step No 1
First of all, you need to install the Following Packages 
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.5" />
### Step NO 2
Create a new class that inherits from `JwtBearerHandler`, which is the base class for JWT authentication handlers in ASP.NET Core:
``` csharp
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;

namespace AC_Projects_API.Helper
{
    public class CustomJwtBearerHandler : JwtBearerHandler
    {
        private readonly HttpClient _httpClient;

        public CustomJwtBearerHandler(HttpClient httpClient, IOptionsMonitor<JwtBearerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
            : base(options, logger, encoder, clock)
        {
            _httpClient = httpClient;
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            // Get the token from the Authorization header
            if (!Context.Request.Headers.TryGetValue("Authorization", out var authorizationHeaderValues))
            {
                return AuthenticateResult.Fail("Authorization header not found.");
            }

            var authorizationHeader = authorizationHeaderValues.FirstOrDefault();
            if (string.IsNullOrEmpty(authorizationHeader) || !authorizationHeader.StartsWith("Bearer "))
            {
                return AuthenticateResult.Fail("Bearer token not found in Authorization header.");
            }

            var token = authorizationHeader.Substring("Bearer ".Length).Trim();

            // Call the API to validate the token
            var response = await _httpClient.GetAsync($"BASE_URL/api/Validate?token={token}");

            // Return an authentication failure if the response is not successful
            if (!response.IsSuccessStatusCode)
            {
                return AuthenticateResult.Fail("Token validation failed.");
            }

            // Deserialize the response body to a custom object to get the validation result
            var validationResult = JsonConvert.DeserializeObject<bool>(await response.Content.ReadAsStringAsync());

            // Return an authentication failure if the token is not valid
            if (!validationResult)
            {
                return AuthenticateResult.Fail("Token is not valid.");
            }

            // Set the authentication result with the claims from the API response          
            var principal = GetClaims(token);

            return AuthenticateResult.Success(new AuthenticationTicket(principal, "CustomJwtBearer"));
        }


        private ClaimsPrincipal GetClaims(string Token)
        {
            var handler = new JwtSecurityTokenHandler();
            var token = handler.ReadToken(Token) as JwtSecurityToken;

            var claimsIdentity = new ClaimsIdentity(token.Claims, "Token");
            var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);

            return claimsPrincipal;
        }
    }
}
```
    
 
### Step NO 3
Register the custom JWT handler in the  in a program.cs:
```csharp

using AC_Projects_API.Helper;
using AC_Projects_API_Domain_Layer.Data;
using AC_Projects_API_Domain_Layer.Models;
using AC_Projects_API_Repository_Layer.IRepository;
using AC_Projects_API_Repository_Layer.Repository;
using AC_Projects_API_Service_Layer.IService;
using AC_Projects_API_Service_Layer.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var ConnectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(ConnectionString));

builder.Services.AddAutoMapper(typeof(Program));

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//Add All Depended Services Here 
builder.Services.AddTransient<CustomJwtBearerHandler>();
builder.Services.AddHttpClient();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddScheme<JwtBearerOptions, CustomJwtBearerHandler>(JwtBearerDefaults.AuthenticationScheme, options => { });
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
builder.Services.AddScoped<ICustomService<Columns>,ColumsService>();
builder.Services.AddScoped<ICustomService<Project>, ProjectsService>();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
    builder =>
    {
        builder.WithOrigins().AllowAnyHeader().AllowAnyMethod();
    });
});

builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebAPIv6", Version = "v1" });
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n 
                      Enter 'Bearer' [space] and then your token in the text input below.
                      \r\n\r\nExample: 'Bearer 12345abcdef'",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });

    c.AddSecurityRequirement(new OpenApiSecurityRequirement()
            {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer"
                            },
                            Scheme = "oauth2",
                            Name = "Bearer",
                            In = ParameterLocation.Header,

                        },
                        new List<string>()
                    }
            });
});

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment() || app.Environment.IsStaging())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseCors();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

```

 
These instructions will enable your custom JWT handler to be used for authentication by your ASP.NET Core Web API. You can edit the `HandleAuthenticateAsync` method in the `CustomJwtHandler` class to incorporate your own logic for token validation, processing, and generating a `ClaimsPrincipal` object.
Don't forget to substitute your own logic and configuration tailored to the needs of your application for the placeholder code in the example.
### Why do we Use JWT Custom Handler? 
You can alter the authentication and authorization procedures for JWT tokens in the ASP.NET Core Web API by using a custom JWT handler. Listed below are a few justifications for using a unique JWT handler:
### Custom Validation Logic: 
You have complete control over the token validation procedure when you develop a unique JWT handler. In accordance with the requirements of your application, you can design unique validation rules. For instance, you could want to check against a unique database or validate extra claims in the token.
### Integration with External Systems:
 A customized JWT handler gives you the ability to create the necessary logic to interface with those systems if you need to integrate your API with external systems or services for token processing or validation. Making API calls to outside services or looking for token revocation lists are two examples of how to do this.
### Token Transformation or Enrichment: 
You can modify or enrich the token before processing it using a custom JWT handler. For instance, depending on circumstances, you might want to add or change new claims.
### Advanced Authorization Scenarios:
 You might occasionally need to use advanced authorization situations that go beyond what the default JWT handler can do. You can design intricate authorization rules based on unique logic, such as attribute- or role-based access restriction, with a custom handler.
### Logging and Auditing: 
You can include logging and auditing features particular to token authentication with a custom JWT handler. For security or debugging purposes, you can log token validations, authentication events, and any other pertinent data.
Employing a customized JWT handler provides you the freedom to customize the token authentication and authorization process to the unique requirements of your application, giving you greater control and flexibility.