How To Use The Identity Framework With Refresh Token Validations

Introduction

In the previous part of the tutorial we learned about how to implement JWT access tokens; In this step-by-step tutorial, I will explain how to use the identity framework with refresh token validations. In this tutorial, we cover the following points.

  • Create a user with an identity framework with custom fields and their use.
  • Create a refresh token with validations of user credentials
  • How to authorize any web API endpoint
  • Validate refresh token and generate a new access token.

Before we start, let’s understand what is refresh token how it works. Let’s look at the following flow diagram

Flow Diagram

As shown in the above flow diagram there are two flows users with an access token and without an access token.

  1. User without access token:
    1. User can send a request to generate a token with its credentials
    2. The server will check credentials and if the credential is validated then generate token
    3. With validation, token users can access resources
  2. With access token:
    1. Validate access token if the token is valid then user access resources
    2. If the token is not valid show an error to the user.
    3. in case of an expired token user can send a request to regenerate the token with a refresh token.
    4. Server will check refresh token validity and if valid generate a new access token and refresh token
    5. So with the new token users can easily get resources.

Step 1 - Install NuGet Packages

Microsoft.AspNetCore.Identity

Microsoft.AspNetCore.Identity.EntityFrameworkCore

Microsoft.Extensions.Identity.Core

Npgsql.EntityFrameworkCore.PostgreSQL

Microsoft.EntityFrameworkCore.Design

Microsoft.EntityFrameworkCore.Tools

Npgsql.EntityFrameworkCore.PostgreSQL.Design

Step 2 - Create Class for users, DBContext, and generate Database

Create a new class named Application user

Add Application User class

And Following code to applications class

using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations.Schema;
namespace WebApplication.DataLayer
{
    [Table("ApplicationUser")]
    public class ApplicationUser:IdentityUser
    {
        [Column("FirstName")]
        public string FirstName { get; set; }
        [Column("LastName")]
        public string LastName { get; set; }
        [Column("RefreshToken")]
        public string? RefreshToken { get; set; }
        [Column("RefreshTokenValidity")]
        public DateTime? RefreshTokenValidity { get; set; }
    }
}

In the above code, we are using Guide as the Primary key and adding new two fields for first name and last name.

The next step is to add a new class for DB Context with the following code.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace WebApplication.DataLayer
{
    public class RefreshTokenDemoContext : IdentityDbContext<ApplicationUser>
    {
        public RefreshTokenDemoContext()
        {
        }
        public RefreshTokenDemoContext(DbContextOptions<RefreshTokenDemoContext> options) : base(options)
        {
        }
        public DbSet<ApplicationUser> applicationUsers { get; set; }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {   
            modelBuilder.Entity<IdentityUserLogin<Guid>>().HasNoKey();
            modelBuilder.Entity<IdentityUserClaim<Guid>>().HasNoKey();
            modelBuilder.Entity<IdentityUserToken<Guid>>().HasNoKey();
            modelBuilder.Entity<IdentityRoleClaim<Guid>>().HasNoKey();
            base.OnModelCreating(modelBuilder);
        }
    }
}

Add a Connection string  to Appsettings.Json Files

"ConnectionStrings": {
    "RefreshTokenDB": "Host=localhost;Port=5432;Database=RefreshTokenDB;Username=postgres;Password=admin123"
  },

After adding the connection string app settings file look alike following

AppSettings

Create an Extension class and method to add Settings in Dependency injections

Right-click solution Add Class- > Named IdentityServicesExtensions with following code

using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using WebApplication.DataLayer;
using Microsoft.AspNetCore.Identity;

namespace WebApplication.Extensions
{
    public static class IdentityServicesExtensions
    {
        public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddDbContext<RefreshTokenDemoContext>(options =>
                options.UseNpgsql(configuration.GetConnectionString("RefreshTokenDB")));

            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddDefaultTokenProviders()
                .AddEntityFrameworkStores<RefreshTokenDemoContext>();
            return services;
        }
    }
}

The next step is to call this extension method in Program.cs file. I m using the extension method because it will not make you program.cs file messy. add the following line.

builder.Services.AddIdentityServices(builder.Configuration);

Once you have added these to your program. cs will be.

Preview Program.cs

Run the Migration to create a database here we are using database code first migration to generate the database.

First Run the add-migration command to then update the database command.

To Run Migration open the Package manager console from Tool> Package manager > Package Manager Console.

Then Run Commands these will output like below.

Run migration

And after successfully adding the migration run command to update the database it will create a database

Step 3 - Register User and Generate Refresh Token

In the Accounts, the controller adds a variable for DbContext class and UserManger like below.

private readonly JwtSettings jwtSettings;
private readonly UserManager<ApplicationUser> userManager;
private readonly RefreshTokenDemoContext refreshTokenDemoContext;

public AccountController(JwtSettings jwtSettings
    , UserManager<ApplicationUser> userManager
    , RefreshTokenDemoContext refreshTokenDemoContext)
{
    this.jwtSettings = jwtSettings;
    this.userManager = userManager;
    this.refreshTokenDemoContext = refreshTokenDemoContext;
}

Add a method to register a user with the following code.

[HttpPost]
public async Task<IActionResult> RegisterUser(Users users)
{
    try
    {
        var applicationUser = new ApplicationUser()
        {
            UserName = users.UserName,
            Email = users.EmailId,
            FirstName = users.FirstName,
            LastName = users.LastName,
            EmailConfirmed = true,
            TwoFactorEnabled = false,
            LockoutEnabled = false,

        };
        var result = await userManager.CreateAsync(applicationUser, users.Password);
        if (result.Succeeded)
        {
            return Ok("User created");
        }
        else
        {
            return BadRequest(result.Errors)
        }
    }
    catch (Exception)
    {
        throw;
    }
}

For generating token replace get token method with following code.

[HttpPost]
public async Task<IActionResult> GetToken(UserLogins userLogins)
{
    try
    {
        var Token = new UserTokens();
        var user = await userManager.FindByNameAsync(userLogins.UserName);
        if (user == null)
        {
            return BadRequest("User name is not valid");
        }
        var Valid = await userManager.CheckPasswordAsync(user, userLogins.Password);
        if (Valid)
        {
            var strToken = Guid.NewGuid().ToString();
            var validity = DateTime.UtcNow.AddDays(15);
            Token = JwtHelpers.JwtHelpers.GenTokenkey(new UserTokens()
            {
                EmailId = user.Email,
                GuidId = Guid.NewGuid(),
                UserName = user.UserName,
                Id = Guid.Parse(user.Id),
                RefreshToken = strToken,

            }, jwtSettings);
            var tokenupdate = refreshTokenDemoContext.Users.Where(x => x.Id == user.Id).FirstOrDefault();
            tokenupdate.RefreshToken = strToken;
            tokenupdate.RefreshTokenValidity = validity;
            refreshTokenDemoContext.Update(tokenupdate);
            refreshTokenDemoContext.SaveChanges();
            Token.RefreshToken = strToken;
        }
        else
        {
            return BadRequest($"wrong password");
        }
        return Ok(Token);
    }
    catch (Exception)
    {
        throw;
    }
}

For generating access token from refresh token add following method.

/// <summary>
/// Generate an Access token
/// </summary>
/// <param name="userLogins"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> RefreshToken(RefreshTokenModel userLogins)
{
    try
    {
        var Token = new UserTokens();
        var user = await userManager.FindByNameAsync(userLogins.UserName);
        if (user == null)
        {
            return BadRequest("User name is not valid");
        }
        var Valid = refreshTokenDemoContext.Users.Where(x=>x.UserName==userLogins.UserName 
        && x.RefreshToken==userLogins.RefreshToken 
        && x.RefreshTokenValidity>DateTime.UtcNow).Count()>0;
        if (Valid)
        {
            var strToken = Guid.NewGuid().ToString();
            var validity = DateTime.UtcNow.AddDays(15);
            Token = JwtHelpers.JwtHelpers.GenTokenkey(new UserTokens()
            {
                EmailId = user.Email,
                GuidId = Guid.NewGuid(),
                UserName = user.UserName,
                Id = Guid.Parse(user.Id),
                RefreshToken = strToken,

            }, jwtSettings);
            var tokenupdate = refreshTokenDemoContext.Users.Where(x => x.Id == user.Id).FirstOrDefault();
            tokenupdate.RefreshToken = strToken;
            tokenupdate.RefreshTokenValidity = validity;
            refreshTokenDemoContext.Update(tokenupdate);
            refreshTokenDemoContext.SaveChanges();
        }
        else
        {
            return BadRequest($"wrong password");
        }
        return Ok(Token);
    }
    catch (Exception)
    {
        throw;
    }
}

Step 4 - Validate Refresh Token

The last step is checking your flow.

First, create a new User.

Get tokens by using a newly created user credential.

Now without validating the token string try to get the user list, and it will show an error.

Validate the token string

Now try to get the user list it will display the user list after the access token expired it will show an error like the below.

Generate new access token from refresh token bypassing access token and refresh token,

After regenerating the token validates the token so you get a list of users

Thank You. Happy Coding.

Summary

In this article, I discussed how we can create a JWT access token.  We also saw how we can authorize the WEB API endpoint.