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

In this article, will see how can we refresh JWT Token in Asp.Net Core Web API, once the access token is expired.
We will see how the token works and how the refresh token re-authenticates the user.

Let’s start with JWT Token Definition

JWT Token Refresh

What is 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 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 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 continually requesting user credentials.

Why do we need Refresh Tokens?

In the event that we are using a JWT token for quite a long time, there is an opportunity a hacker can steal the token and misuse it. Consequently using the JWT token for an extensive long period isn’t exceptionally 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 unauthorize 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. 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 gets expired.
The main benefit of expiring a token in a short time span 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 gets expire 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 life 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 gets expired, 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 gets expired, 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; }
    }

Create an Interface –

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

        string GenerateAccessToken(IEnumerable<Claim> claims);

        string GenerateRefreshToken();

        ClaimsPrincipal GetPrincipalFromExpiredToken(string token);
    }

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, I have defined a token expiry time of 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;

            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
            });
        }
    }

Add below code snippet 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"));

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 could 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, refresh tokens are really helpful. We often provide access tokens with a short expiration period, and when they do, 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
Please follow and like us:

Leave a Comment