Skip to main content

Refresh Token For JWT(JSON Web Token) On Authenticating .Net Core Application

In Part-1 .Net Core Authentication Using JWT(JSON Web Token), we have discussed step by step implementation about generating authentication token using JWT(JSON Web Token). Now we will discuss the generation of refresh token and using refresh token we will fetch authentication token again on its expiration. This article will be the continuation of Part - 1.

RandomNumberGenerator Instance:

System.Security.Cryptography.RandomNumberGenerator will be used to generate a random number which will be used as a refresh token.
Note:
It is not a mandatory approach to use 'System.Security.Cryptography.RandomNumberGenerator'. You can use your own some secured technique to generate a unique token string or you can use GUID.

Generate Refresh Token:

Let's add a private method that returns a random unique key that we can use as a refresh token.
Logic/AccountLogic.cs:
private string GetRefreshToken()
{
	var key = new Byte[32];
	using (var refreshTokenGenerator = RandomNumberGenerator.Create())
	{
		refreshTokenGenerator.GetBytes(key);
		return Convert.ToBase64String(key);
	}
}
  • #L4 at this line System.Security.Cryptography.RandomNumberGenerator.Create() is a static method it will create an instance of the default implementation of a cryptographic random number generator that can be used to generate random data.
  • #L6 at this line generated random number value copied to 'key' variable as an array of byte data.
  • #L7 at this line an array of byte data converted to base64 string format and it will be used as our unique refresh token.

Fetch Refresh Token:

From login endpoint in Part - 1 we returned only Access Token, now we need to push refresh token as well.

As the first step let's update our user table with a new column 'RefreshToken' as below.
The reason behind to store refresh token is to validate it.

Now let's add a new model class that returns an access token and refresh token as below
Models/TokenModel.cs:
namespace JwtApiSample.Models
{
    public class TokenModel
    {
        public string Token { get; set; }
        public string RefreshToken { get; set; }
    }
}
Now let's update the User.cs file with column 'RefreshToken' that added to the User table in the previous step.
Data/Entities/User.cs:
namespace JwtApiSample.Data.Entities
{
    public class User
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }
        public string PhoneNumber { get; set; }
        public string RefreshToken { get; set; }
    }
}
Now let's update the 'GetAuthenticationToken' method in AccountLogic.cs file that will returns both access token and refresh token as below.
Logic/IAccountLogic.cs:
public interface IAccountLogic
{
	TokenModel GetAuthenticationToken(LoginModel loginModel);
}
Logic/AccountLogic.cs:
public TokenModel GetAuthenticationToken(LoginModel loginModel)
{
	User currentUser = _myWorldDbContext.User.Where(_ => _.Email.ToLower() == loginModel.Email.ToLower() &&
	_.Password == loginModel.Password).FirstOrDefault();

	if (currentUser != null)
	{
		var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSettings.Key));
		var credentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256);

		var userCliams = new Claim[]{
			new Claim("email", currentUser.Email),
			new Claim("phone", currentUser.PhoneNumber),
		};

		var jwtToken = new JwtSecurityToken(
			issuer: _tokenSettings.Issuer,
			audience: _tokenSettings.Audience,
			expires: DateTime.Now.AddMinutes(20),
			signingCredentials: credentials,
			claims: userCliams
		);

		string token = new JwtSecurityTokenHandler().WriteToken(jwtToken);
		string refreshToken = GetRefreshToken();

		currentUser.RefreshToken = refreshToken;
		_myWorldDbContext.SaveChanges();


		return new TokenModel
		{
			Token = token,
			RefreshToken = refreshToken
		};
	}

	return null;

}
  • #L25 at this line fetching a private method that returns refresh token.
  • #L27-28 at these lines saving refresh token to the user table.
  • #L31-35 at these lines outputs the TokenModel which holds access token and refresh token.
Let's update the endpoint action method as below
Controllers/AccountController.cs:
[HttpPost]
[Route("login-token")]
public IActionResult GetLoginToken(LoginModel model)
{
	var tokenModel = _accountLogic.GetAuthenticationToken(model);

	if (tokenModel == null)
	{
		return NotFound();
	}
	return Ok(tokenModel);
}
Now test endpoint output as below.

Implement Logic To Generate JWT Token Using RefreshToken:

On the expiration of the JWT token of a user instead of asking the user to enter his credentials for login, we can use refresh token which will regenerate JWT token.

Let's write the logic of refresh token to generate JWT token as follows.
Logic/AccountLogic.cs:
public TokenModel ActivateTokenUsingRefreshToke(TokenModel tokenModel)
{
	var tokenHandler = new JwtSecurityTokenHandler();
	var claimsPrincipal = tokenHandler.ValidateToken(tokenModel.Token,
	new TokenValidationParameters
	{
		ValidateIssuer = true,
		ValidIssuer = _tokenSettings.Issuer,
		ValidateAudience = true,
		ValidAudience = _tokenSettings.Audience,
		ValidateIssuerSigningKey = true,
		IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSettings.Key)),
		ValidateLifetime = true
	}, out SecurityToken validatedToken);


	var jwtToken = validatedToken as JwtSecurityToken;

	if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256))
	{
		return null;
	}

	var email = claimsPrincipal.Claims.Where(_ => _.Type == ClaimTypes.Email).Select(_ => _.Value).FirstOrDefault();
	if (string.IsNullOrEmpty(email))
	{
		return null;
	}

	var currentUser = _myWorldDbContext.User.Where(_ => _.Email == email && _.RefreshToken == tokenModel.RefreshToken).FirstOrDefault();
	if (currentUser == null)
	{
		return null;
	}

	var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSettings.Key));
	var credentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256);

	var newJwtToken = new JwtSecurityToken(
			issuer: _tokenSettings.Issuer,
			audience: _tokenSettings.Audience,
			expires: DateTime.Now.AddMinutes(20),
			signingCredentials: credentials,
			claims: jwtToken.Claims
	);

	string token = new JwtSecurityTokenHandler().WriteToken(newJwtToken);
	string refreshToken = GetRefreshToken();

	currentUser.RefreshToken = refreshToken;
	_myWorldDbContext.SaveChanges();


	return new TokenModel
	{
		Token = token,
		RefreshToken = refreshToken
	};
}
  • #L1 at this line we declared a new method 'ActivateTokenUsingRefreshToken', it takes input model 'TokenModel' where we will post our refresh token and expired or old Jwt token. This method returns also a TokenModel where it will contain newly generated Jwt Access token and refresh token.
  • #L3 at this line we instantiating an object of 'System.IdentityModel.Tokens.Jwt.JwtSecurityHandler'.
  • #L5-L14 at these lines we have used JwtSecuityHandler.ValidateToken(string token, TokenValidationParameters, out SecurityToken validatedToken) method and it return type is 'ClaimsPrincipal'. For this method, we pass our expired token as the first input parameter. As a second parameter, we are passing token validation parameters which are exactly similar to our rules used in the token generation endpoint. Finally, on successful validation of our expired token, we can get the parsed token data as 'validatedToken' out variable. It returns user claims data from the token as well.
  • #L17 at this line 'Microsoft.IdentityModel.Tokens.SecurityToken' is typecast to 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken' which gives more options on our old token information.
  • #L19-21 at these lines checking for token and token algorithm matches as expected or not.
  • #L24-28 at these lines fetching email claim, because using this email we going to check user valid or not and existed in our database or not.
  • #L30-34 at these lines using our refresh token and email from the claims fetching user from the database. if not found null result will be returned to the endpoint.
  • #L36-45 at these lines we can see token generation code similar to what we did in Part - 1 for Jwt token. The only difference here instead of manually adding claims from the database we used claims from old Jwt token. But if you feel like the claims will be updated very frequently then here also add claims from the database manually like in Part - 1.
  • #L47 at this line fetching new Jwt token
  • #L48 at this line fetching new refresh token
  • #L50-51 at these lines we updating the database with a newly generated refresh.
  • #L54-58 at these lines we returning our new Jwt token and refresh token as output.
Logic/IAccountLogic.s:
public interface IAccountLogic
{
	// code hidden for display purpose
	TokenModel ActivateTokenUsingRefreshToke(TokenModel tokenModel);
}

RefreshToken EndPoint:

Let's add a new action method endpoint for the refresh token as below.
Controllers/AccountController.cs:
[HttpPost]
[Route("activate-token-by-refreshtoken")]
public IActionResult ActivateAccessTokenByRefresh(TokenModel refreshToken)
{
	var resultTokenModel = _accountLogic.ActivateTokenUsingRefreshToke(refreshToken);
	if (refreshToken == null)
	{
		return NotFound();
	}
	return Ok(resultTokenModel);
}
Let's test the endpoint and result shows as below

Refactor AccountLogic Code:

We have some common code in login endpoint and refresh token endpoint where we can remove the redundant code.
Logic/AccountLogic.cs:
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using JwtApiSample.Data.Context;
using JwtApiSample.Data.Entities;
using JwtApiSample.Models;
using JwtApiSample.Shared;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

namespace JwtApiSample.Logic
{
    public class AccountLogic : IAccountLogic
    {
        private readonly TokenSettings _tokenSettings;
        private readonly MyWorldDbContext _myWorldDbContext;
        public AccountLogic(
            IOptions<TokenSettings> tokenSettings,
            MyWorldDbContext myWorldDbContext
            )
        {
            _tokenSettings = tokenSettings.Value;
            _myWorldDbContext = myWorldDbContext;
        }

        public TokenModel GetAuthenticationToken(LoginModel loginModel)
        {
            User currentUser = _myWorldDbContext.User.Where(_ => _.Email.ToLower() == loginModel.Email.ToLower() &&
            _.Password == loginModel.Password).FirstOrDefault();

            if (currentUser != null)
            {
                var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSettings.Key));
                var credentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256);

                var userCliams = new List<Claim>{
                    new Claim("email", currentUser.Email),
                    new Claim("phone", currentUser.PhoneNumber),
                };

                return GetTokens(currentUser, userCliams);
            }

            return null;

        }

        private string GetRefreshToken()
        {
            var key = new Byte[32];
            using (var refreshTokenGenerator = RandomNumberGenerator.Create())
            {
                refreshTokenGenerator.GetBytes(key);
                return Convert.ToBase64String(key);
            }
        }

        private TokenModel GetTokens(
            User currentUser,
            List<Claim> userClaims
        )
        {
             var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSettings.Key));
            var credentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256);

            var newJwtToken = new JwtSecurityToken(
                    issuer: _tokenSettings.Issuer,
                    audience: _tokenSettings.Audience,
                    expires: DateTime.Now.AddMinutes(20),
                    signingCredentials: credentials,
                    claims: userClaims
            );

            string token = new JwtSecurityTokenHandler().WriteToken(newJwtToken);
            string refreshToken = GetRefreshToken();

            currentUser.RefreshToken = refreshToken;
            _myWorldDbContext.SaveChanges();


            return new TokenModel
            {
                Token = token,
                RefreshToken = refreshToken
            };
        }

        public TokenModel ActivateTokenUsingRefreshToke(TokenModel tokenModel)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var claimsPrincipal = tokenHandler.ValidateToken(tokenModel.Token,
            new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidIssuer = _tokenSettings.Issuer,
                ValidateAudience = true,
                ValidAudience = _tokenSettings.Audience,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSettings.Key)),
                ValidateLifetime = true
            }, out SecurityToken validatedToken);


            var jwtToken = validatedToken as JwtSecurityToken;

            if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256))
            {
                return null;
            }

            var email = claimsPrincipal.Claims.Where(_ => _.Type == ClaimTypes.Email).Select(_ => _.Value).FirstOrDefault();
            if (string.IsNullOrEmpty(email))
            {
                return null;
            }

            var currentUser = _myWorldDbContext.User.Where(_ => _.Email == email && _.RefreshToken == tokenModel.RefreshToken).FirstOrDefault();
            if (currentUser == null)
            {
                return null;
            }

            return GetTokens(currentUser, jwtToken.Claims.ToList());
        }
    }
}
Successfully we have implemented Jwt token endpoint and refresh token endpoint.

Support Me!
Buy Me A Coffee PayPal Me 

Wrapping Up:

Hopefully, I think this article delivered some useful information about refresh token to regenerate JWT access token in .Net Core application. I love to have your feedback, suggestions, and better techniques in the comment section below.

Refer:


Follow Me:

Comments

  1. ActivateTokenUsingRefreshToke line 95 throws Exception:

    IDX10223: Lifetime validation failed. The token is expired. ValidTo: 'System.DateTime', Current time: 'System.DateTime'.

    ReplyDelete
    Replies
    1. Hi Denis

      Once try to override the 'LifetimeValidator' delegate in TokenValidationParameters object

      like:

      private bool CustomLifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken tokenToValidate, TokenValidationParameters @param)
      {
      if (expires != null)
      {
      return expires > DateTime.UtcNow;
      }
      return false;
      }

      use this custom method in 'TokenValidationParameters.LifetimeValidator'
      like:

      var claimsPrincipal = tokenHandler.ValidateToken(tokenModel.Token,
      new TokenValidationParameters
      {
      ValidateIssuer = true,
      ValidIssuer = _tokenSettings.Issuer,
      ValidateAudience = true,
      ValidAudience = _tokenSettings.Audience,
      ValidateIssuerSigningKey = true,
      IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSettings.Key)),
      ValidateLifetime = true,
      LifetimeValidator = CustomLifetimeValidator
      }, out SecurityToken validatedToken);

      Delete
  2. I have the same issue as Dennis but when I use your example I get the same issue. If I change return expires > DateTime.UtcNow; to return expires < DateTime.UtcNow; It works. Is that correct?

    ReplyDelete
  3. Hi Naveen, I know this article is a few years old, but I stumbled upon it recently and have been studying your code, which is very interesting to me as I'm just learning about refresh tokens. I know from my research that refresh tokens are intended to live longer than access tokens. But in your implementation, there doesn't seem to be any concept of expiration time with respect to the refresh tokens. Does that mean the user is meant to be able to access the system forever without re-authenticating? In what scenario would a user be forced to re-authenticate, other than through the deletion or modification of the refresh token on the server? Also, one other question -- how is this intended to work if there are multiple client devices per user? It seems the server would need to issue/maintain one refresh token per device? Or am I missing something there? Thanks again for this excellent work.

    ReplyDelete

Post a Comment

Popular posts from this blog

Angular 14 Reactive Forms Example

In this article, we will explore the Angular(14) reactive forms with an example. Reactive Forms: Angular reactive forms support model-driven techniques to handle the form's input values. The reactive forms state is immutable, any form filed change creates a new state for the form. Reactive forms are built around observable streams, where form inputs and values are provided as streams of input values, which can be accessed synchronously. Some key notations that involve in reactive forms are like: FormControl - each input element in the form is 'FormControl'. The 'FormControl' tracks the value and validation status of form fields. FormGroup - Track the value and validate the state of the group of 'FormControl'. FormBuilder - Angular service which can be used to create the 'FormGroup' or FormControl instance quickly. Form Array - That can hold infinite form control, this helps to create dynamic forms. Create An Angular(14) Application: Let'

.NET 7 Web API CRUD Using Entity Framework Core

In this article, we are going to implement a sample .NET 7 Web API CRUD using the Entity Framework Core. Web API: Web API is a framework for building HTTP services that can be accessed from any client like browser, mobile devices, and desktop apps. In simple terminology API(Application Programming Interface) means an interface module that contains programming functions that can be requested via HTTP calls either to fetch or update data for their respective clients. Some of the Key Characteristics of API: Supports HTTP verbs like 'GET', 'POST', 'PUT', 'DELETE', etc. Supports default responses like 'XML' and 'JSON'. Also can define custom responses. Supports self-hosting or individual hosting, so that all different kinds of apps can consume it. Authentication and Authorization are easy to implement. The ideal platform to build the REST full services. Install The SQL Server And SQL Management Studio: Let's install the SQL server on our l

ReactJS(v18) JWT Authentication Using HTTP Only Cookie

In this article, we will implement the ReactJS application authentication using the HTTP-only cookie. HTTP Only Cookie: In a SPA(Single Page Application) Authentication JWT token either can be stored in browser 'LocalStorage' or in 'Cookie'. Storing the JWT token inside of the cookie then the cookie should be HTTP Only. The HTTP-ONly cookie nature is that it will be only accessible by the server application. Client apps like javascript-based apps can't access the HTTP-Only cookie. So if we use the authentication with HTTP-only JWT cookie then we no need to implement the custom logic like adding authorization header or storing token data, etc at our client application. Because once the user authenticated cookie will be automatically sent to the server by the browser on every API call. Authentication API: To authenticate our client application with JWT HTTP-only cookie, I developed a NetJS(which is a node) Mock API. Check the GitHub link and read the document on G

.NET6 Web API CRUD Operation With Entity Framework Core

In this article, we are going to do a small demo on AspNetCore 6 Web API CRUD operations. What Is Web API: Web API is a framework for building HTTP services that can be accessed from any client like browser, mobile devices, desktop apps. In simple terminology API(Application Programming Interface) means an interface module that contains a programming function that can be requested via HTTP calls to save or fetch the data for their respective clients. Some of the key characteristics of API: Supports HTTP verbs like 'GET', 'POST', 'PUT', 'DELETE', etc. Supports default responses like 'XML' and 'JSON'. Also can define custom responses. Supports self-hosting or individual hosting, so that all different kinds of apps can consume it. Authentication and Authorization are easy to implement. The ideal platform to build REST full services. Create A .NET6 Web API Application: Let's create a .Net6 Web API sample application to accomplish our

Angular 14 State Management CRUD Example With NgRx(14)

In this article, we are going to implement the Angular(14) state management CRUD example with NgRx(14) NgRx Store For State Management: In an angular application to share consistent data between multiple components, we use NgRx state management. Using NgRx state helps to avoid unwanted API calls, easy to maintain consistent data, etc. The main building blocks for the NgRx store are: Actions - NgRx actions represents event to trigger the reducers to save the data into the stores. Reducer - Reducer's pure function, which is used to create a new state on data change. Store - The store is the model or entity that holds the data. Selector - Selector to fetch the slices of data from the store to angular components. Effects - Effects deals with external network calls like API. The effect gets executed based the action performed Ngrx State Management flow: The angular component needs data for binding.  So angular component calls an action that is responsible for invoking the API call.  Aft

Angular 14 Crud Example

In this article, we will implement CRUD operation in the Angular 14 application. Angular: Angular is a framework that can be used to build a single-page application. Angular applications are built with components that make our code simple and clean. Angular components compose of 3 files like TypeScript File(*.ts), Html File(*.html), CSS File(*.cs) Components typescript file and HTML file support 2-way binding which means data flow is bi-directional Component typescript file listens for all HTML events from the HTML file. Create Angular(14) Application: Let's create an Angular(14) application to begin our sample. Make sure to install the Angular CLI tool into our local machine because it provides easy CLI commands to play with the angular application. Command To Install Angular CLI npm install -g @angular/cli Run the below command to create the angular application. Command To Create Angular Application ng new name_of_your_app Note: While creating the app, you will see a noti

Unit Testing Asp.NetCore Web API Using xUnit[.NET6]

In this article, we are going to write test cases to an Asp.NetCore Web API(.NET6) application using the xUnit. xUnit For .NET: The xUnit for .Net is a free, open-source, community-focused unit testing tool for .NET applications. By default .Net also provides a xUnit project template to implement test cases. Unit test cases build upon the 'AAA' formula that means 'Arrange', 'Act' and 'Assert' Arrange - Declaring variables, objects, instantiating mocks, etc. Act - Calling or invoking the method that needs to be tested. Assert - The assert ensures that code behaves as expected means yielding expected output. Create An API And Unit Test Projects: Let's create a .Net6 Web API and xUnit sample applications to accomplish our demo. We can use either Visual Studio 2022 or Visual Studio Code(using .NET CLI commands) to create any.Net6 application. For this demo, I'm using the 'Visual Studio Code'(using the .NET CLI command) editor. Create a fo

Part-1 Angular JWT Authentication Using HTTP Only Cookie[Angular V13]

In this article, we are going to implement a sample angular application authentication using HTTP only cookie that contains a JWT token. HTTP Only JWT Cookie: In a SPA(Single Page Application) Authentication JWT token either can be stored in browser 'LocalStorage' or in 'Cookie'. Storing JWT token inside of the cookie then the cookie should be HTTP Only. The HTTP-Only cookie nature is that it will be only accessible by the server application. Client apps like javascript-based apps can't access the HTTP-Only cookie. So if we use authentication with HTTP only JWT cookie then we no need to implement custom logic like adding authorization header or storing token data, etc at our client application. Because once the user authenticated cookie will be automatically sent to the server by the browser on every API call. Authentication API: To implement JWT cookie authentication we need to set up an API. For that, I had created a mock authentication API(Using the NestJS Se

ReactJS(v18) Authentication With JWT AccessToken And Refresh Token

In this article, we are going to do ReactJS(v18) application authentication using the JWT Access Token and Refresh Token. JSON Web Token(JWT): JSON Web Token is a digitally signed and secured token for user validation. The JWT is constructed with 3 important parts: Header Payload Signature Create ReactJS Application: Let's create a ReactJS application to accomplish our demo. npx create-react-app name-of-your-app Configure React Bootstrap Library: Let's install the React Bootstrap library npm install react-bootstrap bootstrap Now add the bootstrap CSS reference in 'index.js'. src/index.js: import 'bootstrap/dist/css/bootstrap.min.css' Create A React Component 'Layout': Let's add a React component like 'Layout' in 'components/shared' folders(new folders). src/components/shared/Layout.js: import Navbar from "react-bootstrap/Navbar"; import { Container } from "react-bootstrap"; import Nav from "react-boot

A Small Guide On NestJS Queues

NestJS Application Queues helps to deal with application scaling and performance challenges. When To Use Queues?: API request that mostly involves in time taking operations like CPU bound operation, doing them synchronously which will result in thread blocking. So to avoid these issues, it is an appropriate way to make the CPU-bound operation separate background job.  In nestjs one of the best solutions for these kinds of tasks is to implement the Queues. For queueing mechanism in the nestjs application most recommended library is '@nestjs/bull'(Bull is nodejs queue library). The 'Bull' depends on Redis cache for data storage like a job. So in this queueing technique, we will create services like 'Producer' and 'Consumer'. The 'Producer' is used to push our jobs into the Redis stores. The consumer will read those jobs(eg: CPU Bound Operations) and process them. So by using this queues technique user requests processed very fastly because actually