Level Up API Authentication with JWT Refresh Token in ASP.NET CORE and Angular

In this article, will see how can we refresh the JWT Token in Asp.Net Core Web API, once the access token has expired.
We will see how the token works and how the JWT refresh token re-authenticates the user. To demonstrate this feature we will use ASP.NET Core API and Angular.

Let’s start with the JWT Token Definition.

JWT Token Refresh

What is a JWT Token?

In the world of web applications, a JWT (JSON Web Token) is like that secure box. It’s a way to securely share data between two parties – usually a client and a server. The JWT contains a piece of confidential information (like a user’s identity) that has been encrypted or signed to ensure its authenticity.

JWT Token

Lifecycle of JWT Token

  • Create a JWT Token once a user logs into the application.
  • Send JWT and stores at the client’s browser.
  • Include JWT Token in the header of each HTTP request.
  • Validate JWT on the server side.
  • Verify the JWT Token and proceed further.
  • Expire JWT after a defined time frame.
  • Renewal or Refresh JWT Token.

This article will concentrate on how to automatically refresh JWT tokens without requesting user credentials every time.

Why do we need Refresh Tokens?

If we have been using the same JWT token for quite a long time in an application, there is an opportunity for a hacker can steal the token and misuse it. So, consequently using the same JWT token for an extensive long period isn’t safe.

Refresh tokens are the sort of tokens that can be utilized to get new access tokens. At the point when the JWT tokens lapse or expire, we can utilize a new Token for authentication using a refresh token.

We will set a short lifetime for an access token. So that, even the token utilized by an unauthorized user gets access just for a limited period. We will issue a refresh token alongside an access token once the user authenticates for the first time. So, whenever the access token expires, we can get another access token with the help of a refresh token.

Benefits of using Refresh Token Mechanism

Let’s talk about the benefits of the Refresh Token mechanism.
First, we will understand what will happen if a Token expires.
The main benefit of expiring a token in a short period is to avoid unauthorized access even if the token is utilized by a hacker.
The drawback is once the token expires the authorized user needs to re-login again, the cycle continues and is overhead for users using an application that needs more time to complete the transactions.

Now, think of that a token is accessed by an unauthorized user in this case once the token expires and a new token is generated for an authorized user. Now, unauthorized users will not be able to communicate with the server using a previous access token.

On the other side, for authorized users a tokens get refreshed automatically without prompting login and password, this will make authorized user’s lives easier.

So, the refresh token re-generates a new JWT token for the user instead of prompting login credentials again.

How does JWT Refresh Token Work?

When the ongoing Token expires, the frontend code will call the refresh token method. It will take a look at the token details and credentials, if they match it’ll send a refresh token and access token accordingly.

When the JWT Token expires, you may validate the refresh token and principal identity.

The method GetPrincipalFromExpiredToken accepts the current token and will return the user’s identity such as username. Post validation, it generates the new access token and refresh token and sends it back to the client.

JWT Token Refresh

Implement JWT Token authentication with Refresh Token mechanism in .NET Core with Angular

To implement and demonstrate the Refresh Token mechanism, I have built a .NET Core Web API and a basic Angular web application.

Create a .NET Core Web API

Create a .NET Core Web API project in Visual Studio 2019. You may use Visual Studio 2022 as well.

I am using the default controller i.e. WeatherForecast controller to demonstrate this.

WeatherForecast Controller class.

Add Authorize attribute with controller class to avoid unauthorized access.

    [Authorize]
    [ApiController]
    [Route("api/[controller]")]
    public class WeatherForecastController : ControllerBase
    {

        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
            
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
}

Define models for this demo.

        public class Users
        {
            public string UserName { get; set; }
            public string Password { get; set; }
        }

        public class TokenApiModel
        {
            public string? jwtToken { get; set; }
            public string? refreshToken { get; set; }
        }

        public class AuthenticatedResponse
        {
            public string? Token { get; set; }
            public string? RefreshToken { get; set; }
        }

    public class WeatherForecast
    {
        public DateTime Date { get; set; }

        public int TemperatureC { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

        public string Summary { get; set; }
    }

Let’s create an Interface –

public interface IJWTTokenAuth
    {
        bool Authenticate(string username, string password);

        string GenerateAccessToken(IEnumerable<Claim> claims);

        string GenerateRefreshToken();

        ClaimsPrincipal GetPrincipalFromExpiredToken(string token);
    }

Next, we will create a class and implement the interface:

public class JWTToeknAuth : IJWTTokenAuth
    {
        private readonly string key;
        public JWTToeknAuth(string key)
        {
            this.key = key;
        }
        public bool Authenticate(string username, string password)
        {
            //write code to authenticate user credential from database
            if (username == "user1" && password == "12345")
            {
                return true;
            }

            else
                return false;
        }

        public string GenerateAccessToken(IEnumerable<Claim> claims)
        {
            var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MyConfidentialKey"));
            var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
            var tokeOptions = new JwtSecurityToken(
                issuer: "https://localhost:5000",
                audience: "https://localhost:5000",
                claims: claims,
                expires: DateTime.Now.AddSeconds(60),
                signingCredentials: signinCredentials                
            );
            var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
            return tokenString;
        }


        public string GenerateRefreshToken()
        {
            var randomNumber = new byte[32];
            using (var rng = RandomNumberGenerator.Create())
            {
                rng.GetBytes(randomNumber);
                return Convert.ToBase64String(randomNumber);
            }
        }

        public ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
        {
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateAudience = false,
                ValidateIssuer = false,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MyConfidentialKey")),
                ValidateLifetime = false
            };
            var tokenHandler = new JwtSecurityTokenHandler();
            SecurityToken securityToken;
            var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
            var jwtSecurityToken = securityToken as JwtSecurityToken;
            if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
                throw new SecurityTokenException("Invalid token");
            return principal;
        }

    }

In the above code snippet, the token expiry time is 60 seconds.

Create a controller and name it JWTController.

Add below action methods in this controller class –

Action NameHttp VerbPurpose
jwtauthHttpPostTo authenticate users and generate JWT Token
refreshjwtHttpPostTo refresh Token when Token gets expired.
    [Route("api/[controller]")]
    [ApiController]
    public class JWTController : ControllerBase
    {

        private readonly IJWTTokenAuth jWTTokenAuth;
        private readonly ILogger<WeatherForecastController> _logger;
        public JWTController(ILogger<WeatherForecastController> logger, IJWTTokenAuth jWTTokenAuth)
        {
            _logger = logger;
            this.jWTTokenAuth = jWTTokenAuth;
        }

        [AllowAnonymous]
        [HttpPost("jwtauth")]
        public IActionResult Authenticate([FromBody] Users usrs)
        {
            if (!jWTTokenAuth.Authenticate(usrs.UserName, usrs.Password))
                return Unauthorized();

            var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, usrs.UserName),
            new Claim(ClaimTypes.Role, "User")
        };
            var accessToken = jWTTokenAuth.GenerateAccessToken(claims);
            var refreshToken = jWTTokenAuth.GenerateRefreshToken();
            
            return Ok(new AuthenticatedResponse
            {
                Token = accessToken,
                RefreshToken = refreshToken
            });
        }

        [AllowAnonymous]
        [HttpPost("refreshjwt")]
        public IActionResult RefreshToken(TokenApiModel tokenApiModel)
        {
            if (tokenApiModel is null)
                return BadRequest("Invalid client request");

            string accessToken = tokenApiModel.jwtToken;
            string refreshToken = tokenApiModel.refreshToken;

            var principal = jWTTokenAuth.GetPrincipalFromExpiredToken(accessToken);
            var username = principal.Identity.Name;
            // We can validate the user's identity from a database.
           //We can now validate the Refresh Token to generate a new JWT Token.

            if (username is null)
                return BadRequest("Invalid client request");

            var newAccessToken = jWTTokenAuth.GenerateAccessToken(principal.Claims);
            var newRefreshToken = jWTTokenAuth.GenerateRefreshToken();

            return Ok(new AuthenticatedResponse()
            {
                Token = newAccessToken,
                RefreshToken = newRefreshToken
            });
        }
    }

A refresh token validation can be done from storage and once the refresh token is used, we should replace the old refresh token with the new one, and so on.

If you have used Entity Framework within your code, then it will be easy and simple to validate the user’s identity with the refresh token. We can use below code snippet below to fetch the user information from the database using the refresh token

var user = _dbcontext.Users.SingleOrDefault(u => u.RefreshTokens.Any(t => t.Token == token));
  if (user == null)
           // throw Exception

Add below code snippet below within ConfigureServices method available in startup.cs file

 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(x =>
            {
                var serverSecret = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("MyConfidentialKey"));
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = false, // on production make it true
                    ValidateAudience = false, // on production make it true
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = serverSecret,
                    ValidIssuer = "http://localhost:5000/",
                    ValidAudience = "http://localhost:5000/",
                    ClockSkew = TimeSpan.Zero
                };
                x.Events = new JwtBearerEvents
                {
                    OnAuthenticationFailed = context =>
                    {
                        if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                        {
                            context.Response.Headers.Add("IS-TOKEN-EXPIRED", "true");
                        }
                        return Task.CompletedTask;
                    }
                };
            });
            services.AddSingleton<IJWTTokenAuth>(new JWTToeknAuth("MyConfidentialKey"));

Here, the ValidIssuer and ValidAudience value is the API URL.

Create Angular application

Create an Angular application to consume the Web API.

Add a new component using this command – ng g component home

<h1  style="font-size: 60px;text-align:center;color: blue;">Welcome !!!</h1>
<br>
<br>

<h1  style="font-size: 45px;text-align:center;">Weather Report</h1>
<br>

{{this.tokenmsg.tokenRefreshMsg}} @ {{newTokenTime}}

<br>

<button type="submit" class="btn btn-primary" (click)="CheckWeather()">Check Weather</button> 
<button type="submit" class="btn btn-primary" (click)="ClearToken()">Clear Token</button> 

<table>
    <tr>
        <th>Date</th>
        <th>Temp</th>
        <th>Summary</th>
    </tr>
    <tr *ngFor="let row of weatherdata">
        <td>{{row.date}}</td>
        <td>{{row.temprature}}</td>
        <td>{{row.summary}}</td></tr>
</table>

Component ts file

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { WeatherReportService } from '../weather-report.service';
import { LoginService } from '../service/login.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
 
  auth_token: string | null | undefined;
  welcomemsg: string | undefined;
  weatherdata:any;
  newTokenTime = new Date();
 
  constructor(private _router: Router, private weatherService: WeatherReportService, public tokenmsg: LoginService) { }

  ngOnInit(): void {
    this.auth_token = localStorage.getItem("token");
    if(!this.auth_token || this.auth_token == null || this.auth_token === "")
    {
      this._router.navigateByUrl('/login') 
    }
    else{
    this.welcomemsg = "Welcome"
    }
  }

  ClearToken()
  {
    localStorage.setItem("token", '');
    localStorage.setItem("refreshtoken", '');
    this._router.navigateByUrl('/login') 
  }

  CheckWeather() {
    this.auth_token = localStorage.getItem("token");
    if(!this.auth_token || this.auth_token == null || this.auth_token === "")
    {
      this._router.navigateByUrl('/login') 
    }
    else{
      this.weatherService.getWeatherData().subscribe(response => {
        console.log(response);
        this.weatherdata = response;
     }
   );
    }
    
  
  }

}

Create a service with the name login.

Use this command to create a new service file- ng g service login

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http'
import { Router } from '@angular/router';
import { Subject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class LoginService {
  apiurl = 'http://localhost:5000/api/'
  response: any;

  tokenRefreshMsg: string = '';


  constructor(private http: HttpClient, private router: Router) { }

  tokenresp: any;
  private _updatemenu = new Subject<void>();
  get updatemenu() {
    return this._updatemenu;
  }

  AuthenticateUser(usercred: any) {
    const httpHeaders = { headers:new HttpHeaders({'Content-Type': 'application/json'}) };
    this.response = this.http.post<String>(this.apiurl + 'JWT/jwtauth', usercred, httpHeaders);
    return this.response;
  }

  GenerateRefreshToken() {
    let input = {
      "jwtToken": this.GetToken(),
      "refreshToken": this.GetRefreshToken()
    }
    return this.http.post(this.apiurl + 'JWT/refreshjwt', input);
  }

  IsLogged() {
    return localStorage.getItem("token") != null;
  }
  GetToken() {
    return localStorage.getItem("token") || '';
  }
  GetRefreshToken() {
    return localStorage.getItem("refreshtoken") || '';
  }

  SaveTokens(tokendata: any) {
    localStorage.setItem('token', tokendata.token);
    localStorage.setItem('refreshtoken', tokendata.refreshToken);
  }

  Logout() {
    alert('Your session expired')
    localStorage.clear();
    this.router.navigateByUrl('/login');
  }
}

Create a service for Weather Report with the name – WeatherReportService

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class WeatherReportService {
  url = 'http://localhost:5000/api/';
  auth_token : any
  response: any
  constructor(private http: HttpClient) { }

  
  getWeatherData() {
    
    this.auth_token = localStorage.getItem("jwt");
    const headerDict = {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'Access-Control-Allow-Headers': 'Content-Type',
      'Authorization': 'Bearer ' + this.auth_token
   
    }

    const httpHeaders = { headers:new HttpHeaders(headerDict)};
    return this.http.get(this.url + 'WeatherForecast',  httpHeaders);
  }
}

Create an interceptor service and name it token-interceptor

import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';

import { Injectable, Injector } from '@angular/core';
import { catchError, Observable, throwError, BehaviorSubject, switchMap, filter, take } from 'rxjs';
import { LoginService } from './login.service';

@Injectable({
  providedIn: 'root'
})
export class TokenInterceptorService implements HttpInterceptor {

  constructor(private inject: Injector, private login: LoginService) { }
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    let authservice = this.inject.get(LoginService);
    let authreq = request;
    authreq = this.AddTokenheader(request, authservice.GetToken());
    return next.handle(authreq).pipe(
      catchError(errordata => {
        if (errordata.status === 401) {
           this.login.tokenRefreshMsg = "Token Refreshed";
          return this.handleRefrehToken(request, next); // Call to refresh Token or route to login page
        }
        return throwError(errordata);
      })
    );

  }

  handleRefrehToken(request: HttpRequest<any>, next: HttpHandler) {
    let authservice = this.inject.get(LoginService);
    return authservice.GenerateRefreshToken().pipe(
      switchMap((data: any) => {
        authservice.SaveTokens(data);
        return next.handle(this.AddTokenheader(request,data.token))
      }),
      catchError(errodata=>{
        console.log(errodata);
        authservice.Logout();
        return throwError(errodata)
      })
    );
  }

  AddTokenheader(request: HttpRequest<any>, token: any) {
    return request.clone({ headers: request.headers.set('Authorization', 'bearer ' + token) });
  }

}

In the developer tools of the browser, we can see the token in the Authorization header when we call the authorized method.

Whenever the refresh token method is called, you can see the Refreshed token time on the screen.

Takeaway

In this article, we learned how to secure our.NET Core Web API application using refresh tokens and JWT access tokens. To further increase application security, JWT refresh tokens are helpful. So, we often provide access tokens with a short expiration period, and when they expire, we use refresh tokens to obtain new access tokens. Therefore, if an attacker gets this access token, they will not be able to utilize it indefinitely.

JWT Token Authentication in Angular and .NET Core

Leave a Comment

RSS
YouTube
YouTube
Instagram