Create And Validate JWT Token In .NET 5.0

Introduction

In this article, we'll cover how we can create and configure JWT Bearer Authentication and Authorization for APIs built with .Net 5.0. There are plenty of resources out which cover "JWT Auth" but in this article, we'll be focusing on the implementation of custom JWT Authentication with Custom JWT middleware and a custom authorize attribute.

JWT Authentication

Prerequisites

  • Visual Studio 2019 - Download from here
  • .Net 5.0 SDK - Download from here

Topics to be covered

  1. Setup the .Net 5.0 Web API Project.
  2. Configure JWT Authentication
  3. Generate JWT Token.
  4. Validate JWT Token using Custom Middleware and Custom Authorize Attribute.
  5. Testing the Endpoint (API) with Swagger.

Setup the .Net 5.0 Web API project 

Open Visual Studio and select "Create a new project" and click the "Next" button.

Add the "project name" and "solution name" also the choose the path to save the project in that location, click on "Next".

Now choose the target framework ".Net 5.0" which we get once we install the SDK and also will get one more option to configure Open API support by default with that check box option.

Configure JWT Authentication

To configure the JWT(JSON web tokens) we must have the Nuget package installed inside the project, so let's first add the project dependencies.

NuGet Packages to be installed

Inside the Visual Studio - Click on Tools -> Nuget Package Manager -> Manage Nuget packages for solution.

Install through Console,

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 5.0.7

The first step is to configure the JWT authentication in our project. To do this, we need to register a JWT schema inside the swagger service by using the "AddAuthentication" method and specifying by passing the values.

Let's define the Swagger Service and attach the JWT Auth code,

#region Swagger Configuration
            services.AddSwaggerGen(swagger =>
            {
                //This is to generate the Default UI of Swagger Documentation  
                swagger.SwaggerDoc("v1", new OpenApiInfo
                {
                    Version = "v1",
                    Title = "JWT Token Authentication API",
                    Description = "ASP.NET Core 5.0 Web API"
                });
                // To Enable authorization using Swagger (JWT)
                swagger.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
                {
                    Name = "Authorization",
                    Type = SecuritySchemeType.ApiKey,
                    Scheme = "Bearer",
                    BearerFormat = "JWT",
                    In = ParameterLocation.Header,
                    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\"",
                });
                swagger.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                          new OpenApiSecurityScheme
                            {
                                Reference = new OpenApiReference
                                {
                                    Type = ReferenceType.SecurityScheme,
                                    Id = "Bearer"
                                }
                            },
                            new string[] {}
                    }
                });
            });
            #endregion

Add Service Authentication to perform the Auth Scheme and its challenge and add the JWTBearer to Authorize the token through the Swagger.

#region Authentication
            services.AddAuthentication(option =>
            {
                option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

            }).AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = false,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = Configuration["Jwt:Issuer"],
                    ValidAudience = Configuration["Jwt:Audience"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])) //Configuration["JwtToken:SecretKey"]
                };
            });
            #endregion

In the above example, we have specified the parameters that must be taken into consideration to validate the token.

  • Validate the server (ValidateIssuer = true) that generates the token.
  • Validate the recipient of the token is authorized to receive (ValidateAudience = true)
  • Check if the token is not expired and the signing key of the issuer is valid (ValidateLifetime = true)
  • Validate signature of the token (ValidateIssuerSigningKey = true)

We have to specify the values for "Audience", "Issuer" and "Secret key" in this project we have stored these values inside the appsettings.json file.

appsettings.json

"Jwt": {
    "Key": "SecretKey10125779374235322",
    "Issuer": "https://localhost:44341",
    "Audience": "http://localhost:4200"
  },

Generate JWT Token

Let's create a controller named AuthController inside the controller folder and add the Auth method which is responsible to validate the login credentials and create the token based on username. We have marked this method with the AllowAnonymous attribute to bypass the authentication. This method expects LoginModel object for username and password.

We have created a service folder in which our core business logic is residing and we are using dependency injection to consume these services in the controller via constructor injection.

For demo purposes, I have hardcoded the values inside the method to validate the model.

UserService.cs

 public bool IsValidUserInformation(LoginModel model)
        {
            if (model.UserName.Equals("Jay") && model.Password.Equals("123456")) return true;
            else return false;
        }

IUserService.cs

 public interface IUserService
    {
        bool IsValidUserInformation(LoginModel model);       
    }

Add this service to the Startup class under the configure services method.

services.AddTransient<IUserService,UserService>();

Inside the Auth controller, let us create the private method known as "GenerateJwtToken" to create a token based on the Issuer, Audience, and Secretkey which we defined in the appsettings.json file.

AuthController.cs

using JWTAuth_Validation.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.ComponentModel.DataAnnotations;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace JWTAuth_Validation.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        private readonly IConfiguration _configuration;
        private readonly IUserService _userService;
        public AuthController(IConfiguration configuration,IUserService userService)
        {
            _configuration = configuration;
            _userService = userService;
        }
        [AllowAnonymous]
        [HttpPost(nameof(Auth))]
        public IActionResult Auth([FromBody] LoginModel data)
        {
            bool isValid = _userService.IsValidUserInformation(data);
            if (isValid)
            {
                var tokenString = GenerateJwtToken(data.UserName);
                return Ok(new { Token = tokenString, Message = "Success" });
            }
            return BadRequest("Please pass the valid Username and Password");
        }
        [Authorize(AuthenticationSchemes = Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme)]
        [HttpGet(nameof(GetResult))]
        public IActionResult GetResult()
        {
            return Ok("API Validated");
        }
        /// <summary>
        /// Generate JWT Token after successful login.
        /// </summary>
        /// <param name="accountId"></param>
        /// <returns></returns>
        private string GenerateJwtToken(string userName)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_configuration["Jwt:key"]);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new[] { new Claim("id", userName) }),
                Expires = DateTime.UtcNow.AddHours(1),
                Issuer = _configuration["Jwt:Issuer"],
                Audience = _configuration["Jwt:Audience"],
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }

    }
    #region JsonProperties  
    /// <summary>  
    /// Json Properties  
    /// </summary>  
    public class LoginModel
    {
        [Required]
        public string UserName { get; set; }
        [Required]
        public string Password { get; set; }
    }
    #endregion
}

Once we enabled the Authentication, I have created a sample Get API by adding the Authorize Attribute So that this API will trigger the validation check of the token passed with an HTTP request.

If someone tries to access this API without the proper token, it will throw a 401 (Unauthorized Access) as a response. If we want to bypass the authentication for any of our existing methods, we can mark that method with the AllowAnonymous attribute.

Validate JWT Token using Custom Middleware and Custom Authorize Attribute

Below is the custom JWT middleware that validates the token in the request "Authorization" header if it exists. On successful validation, the middleware retrieves that associated user from the database and assigns it to its context.Items["User"] makes the current account available to any other code running within the current request scope, which we'll use below in a custom authorization attribute.

Create a folder named Middleware in which will add the JWTMiddleware and AuthorizeAttribute class.

JWTMiddleware.cs

using JWTAuth_Validation.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace JWTAuth_Validation.Middleware
{
    public class JWTMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IConfiguration _configuration;
        private readonly IUserService _userService;

        public JWTMiddleware(RequestDelegate next, IConfiguration configuration, IUserService userService)
        {
            _next = next;
            _configuration = configuration;
            _userService = userService;
        }

        public async Task Invoke(HttpContext context)
        {
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();

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

            await _next(context);
        }

        private void attachAccountToContext(HttpContext context, string token)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]);
                tokenHandler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    // 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 = jwtToken.Claims.First(x => x.Type == "id").Value;

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

AuthorizeAttribute.cs

using JWTAuth_Validation.Controllers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;

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

Let's inject this Middleware into the Startup class.

 app.UseMiddleware<JWTMiddleware>();

So each time whoever hits the API with the Authorization header it will validate the token first and sends the appropriate response in the body.

How did JWTMiddleware work?

If(ValidToken) "Authorized"

else "UnAuthorized"

Testing the API with Swagger (OpenAPI)

Run the application; it will take us to the Swagger index page with all the configuration setup we made in the project.

Let's pass the valid credentials to the Auth API to get the access token.

{
  "userName": "Jay",
  "password": "123456"
}

Copy the token and add the same token in the Authorize button followed by Bearer "Token".

Now all the APIs are Authorized in the Swagger. Let us test the Get API.

Conclusion

JWT is very famous in web development. It is an open standard that allows transmitting data between parties as a JSON object in a secure and compact way. In this article, we learned how to create and Validate JWT with ASP.NET core application.

Thank you for reading, please let me know your questions, thoughts, or feedback in the comments section. I appreciate your feedback and encouragement.

You can view or download the source code from the GitHub link here.

keep Learning ...!