Client-side Application For JWT Refresh Token In Angular 13

Introduction

Refresh tokens are the kind of tokens that can be used to get new access tokens. When the access tokens expire, we can use refresh tokens to get a new access token from the authentication controller. The lifetime of a refresh token is usually much longer compared to the lifetime of an access token.

I have already authored a detailed article about JWT Refresh tokens in .NET 6.0 on C# Corner.

You can read the article and download the entire source code from the link below.

JWT Authentication with Refresh Tokens in .NET 6.0

In this article, we will see all the steps to create a client-side application for JWT refresh token with Angular 13 version.

I am using the latest version of Angular CLI (as on February 13th). You must download the compatible version of Node JS.

Modify the existing .NET 6.0 backend application

We are using the same source code of backend application (.NET 6.0) that we have used in the earlier article.

We must add the code changes below in Program.cs file to enable CORS (Cross Origin Resource Sharing)

Program.cs 

// Partial Code for Program.cs

var MyAllowedOrigins = "_myAllowedOrigins";

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowedOrigins,
                          builder =>
                          {
                              builder.WithOrigins("http://localhost:4200")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
});

var app = builder.Build();

app.UseCors(MyAllowedOrigins);

We can add a new Web API controller inside the Controller folder for our Address Book application. We are simply returning a few hard coded Address book data from this controller. We will use this Address book data in our Angular application later.

AddressesController.cs 

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace JWTRefreshToken.NET6._0.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class AddressesController : ControllerBase
    {
        // GET: api/<AddressesController>
        [HttpGet]
        public IEnumerable<Address> Get()
        {
            List<Address> addresses = new()
            {
                new Address { Name = "Sarathlal Saseendran", HouseName = "Chakkalayil House", City = "Karunagappally", State = "Kerala", Pin = 690574 },
                new Address { Name = "Aradhya Sarathlal", HouseName = "Chakkalayil House", City = "Karunagappally", State = "Kerala", Pin = 690574 },
                new Address { Name = "Anil Soman", HouseName = "Karoor Illam", City = "Oachira", State = "Kerala", Pin = 690526 },
            };
            return addresses;
        }
    }

    public class Address
    {
        public string? Name { get; set; }
        public string? HouseName { get; set; }
        public string? City { get; set; }
        public string? State { get; set; }
        public int Pin { get; set; }
    }
}

Please note that we have added an Authorize attribute in this controller. So that, nobody can access this controller without proper permission.

We can create our Angular 13 application from scratch.

Create Angular 13 application using Angular CLI

Use the below command to create a new angular application using Angular CLI.

ng new JWTRefreshTokenAngular13 

Angular CLI will ask you about adding routing to the application. We have opted for routing with this application. We have also chosen CSS as the default stylesheet format for our application.

Our new application will be created in a few moments.

We must install the libraries below inside our application.

  • bootstrap 
  • font-awesome 
  • ngx-toastr 
  • @auth0/angular-jwt 

Bootstrap and font-awesome libraries are used for application styling and ngx-toastr is used for some beautiful notification messages. @auth0/agular-jwt is an important library used for checking the access token expiry inside our application.

npm install bootstrap font-awesome ngx-toastr @auth0/angular-jwt 

Above single npm command will install all four libraries into our Angular application.

We must change angular.json file with below code change.

We have added a stylesheet configuration for toaster notification.

We can add one property inside the environment variable. This property is used for storing backend base URL. So that, we can use this base URL multiple times in our application without hard coding.

environment.ts 

export const environment = {
  production: false,
  baseUrl: "http://localhost:5000/api/"
};

We need our own interceptor to add JWT token to the header of each request. We can create our interceptor.

ng g class MyInterceptor 

my-interceptor.ts

import { Injectable } from "@angular/core";
import {
    HttpInterceptor, HttpHandler, HttpRequest,
} from '@angular/common/http';

@Injectable()
export class MyInterceptor implements HttpInterceptor {

    intercept(request: HttpRequest<any>, next: HttpHandler) {

        request = request.clone({ headers: request.headers.set('Content-Type', 'application/json') });
        let token: string | null = localStorage.getItem("accessToken");
        if (token) {
            request = request.clone({ headers: request.headers.set('Authorization', 'Bearer ' + token) });
        }
        return next.handle(request);
    }

}

We will store the JWT access token inside the local storage once we receive it from backend application. We will add this token to the request header using the interceptor. We will also set content type as application/json using interceptor.

We can create the notification service now.

ng g service Notification

notification.service.ts 

import { Injectable } from '@angular/core';
import { ToastrService } from 'ngx-toastr';

@Injectable({
  providedIn: 'root'
})
export class NotificationService {

  constructor(private toastr: ToastrService) { }

  showSuccess(message: string, title: string) {
    this.toastr.success(message, title)
  }

  showError(message: string, title: string) {
    this.toastr.error(message, title)
  }

  showInfo(message: string, title: string) {
    this.toastr.info(message, title)
  }

  showWarning(message: string, title: string) {
    this.toastr.warning(message, title)
  }
}

Notification service is used for creating various toaster messages for successful, error and information type messages.

We can create our authentication guard now.

ng g service guard/AuthGuard 

auth-guard.service.ts 

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { JwtHelperService } from '@auth0/angular-jwt';
import { lastValueFrom } from 'rxjs';
import { environment } from 'src/environments/environment';
import { NotificationService } from '../notification.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  public jwtHelper: JwtHelperService = new JwtHelperService();

  constructor(private router: Router, private http: HttpClient, private notification: NotificationService) {
  }
  async canActivate() {
    const token = localStorage.getItem("accessToken");

    if (token && !this.jwtHelper.isTokenExpired(token)) {
      return true;
    }

    const isRefreshSuccess = await this.refreshingTokens(token);
    if (!isRefreshSuccess) {
      this.router.navigate(["login"]);
    }

    return isRefreshSuccess;
  }

  private async refreshingTokens(token: string | null): Promise<boolean> {
    const refreshToken: string | null = localStorage.getItem("refreshToken");

    if (!token || !refreshToken) {
      return false;
    }

    const tokenModel = JSON.stringify({ accessToken: token, refreshToken: refreshToken });

    let isRefreshSuccess: boolean;
    try {

      const response = await lastValueFrom(this.http.post(environment.baseUrl + "authenticate/refresh-token", tokenModel));
      const newToken = (<any>response).accessToken;
      const newRefreshToken = (<any>response).refreshToken;
      localStorage.setItem("accessToken", newToken);
      localStorage.setItem("refreshToken", newRefreshToken);
      this.notification.showSuccess("Token renewed successfully", "Success")
      isRefreshSuccess = true;
    }
    catch (ex) {
      isRefreshSuccess = false;
    }
    return isRefreshSuccess;
  }

}

Auth guard will check the access token expiry and once it is expired, it will try to refresh using refresh token. If the refresh token is successful, a new access token and refresh token will be replaced in local storage.

Please note that I have added a notification message in token refresh time. This is not needed in real application. I just added it for testing purposes.

We can create our components now.

First, we can create Login component.

ng g component Login 

Copy the code below for component class file.

login.component.ts 

import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { environment } from 'src/environments/environment';
import { NotificationService } from '../notification.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
  public invalidLogin: boolean = false;

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

  ngOnInit(): void {
  }

  public login = (form: NgForm) => {
    const credentials = JSON.stringify(form.value);

    this.http.post(environment.baseUrl + "authenticate/login",
      credentials
    ).subscribe({
      next: (response) => {
        this.notification.showSuccess("User login successful", "Success")
        const token = (<any>response).token;
        const refreshToken = (<any>response).refreshToken;
        localStorage.setItem("accessToken", token);
        localStorage.setItem("refreshToken", refreshToken);
        this.invalidLogin = false;
        this.router.navigate(["/"]);
      },
      error: (err) => {
        this.notification.showError("Invalid username or password.", "Error")
        console.error(err)
        this.invalidLogin = true;
      },
      complete: () => console.info('Login complete')
    });
  }
}

Copy the code below for component HTML file.

login.component.html 

<form class="form-signin" #loginForm="ngForm" (ngSubmit)="login(loginForm)">
    <div class="container-fluid">
        <h2 class="form-signin-heading">Login</h2>
        <div *ngIf="invalidLogin" class="alert alert-danger">Invalid username or password.</div>
        <br/>
        <label for="username" class="sr-only">User Name</label>
        <input type="email" id="username" name="username" ngModel class="form-control" placeholder="User Name" required autofocus>
        <br/>
        <label for="password" class="sr-only">Password</label>
        <input type="password" id="password" name="password" ngModel class="form-control" placeholder="Password" required>
        <br/>
        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
    </div>
</form>

We can create the Register component now.

ng g component Register 

Copy the code below for component class file.

register.component.ts 

import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { environment } from 'src/environments/environment';
import { NotificationService } from '../notification.service';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit {
  public invalidRegister = false;

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

  ngOnInit(): void {
  }

  public register = (form: NgForm) => {
    const registerModel = JSON.stringify(form.value);

    this.http.post(environment.baseUrl + "authenticate/register",
      registerModel
    ).subscribe({
      next: () => {
        this.invalidRegister = false;
        this.notification.showSuccess("New user registered successfully", "Success")
        this.router.navigate(["/login"]);
      },
      error: (err) => {
        this.notification.showError("User already exists / register user failed", "Error")
        console.error(err)
        this.invalidRegister = true;
      },
      complete: () => console.info('Register complete')
    });
  }
}

Copy the code below for component HTML file.

register.component.html 

<form class="form-signin" #registerForm="ngForm" (ngSubmit)="register(registerForm)">
    <div class="container-fluid">
        <h2 class="form-signin-heading">Register</h2>
        <div *ngIf="invalidRegister" class="alert alert-danger">User already exists / register user failed.</div>
        <br/>
        <label for="username" class="sr-only">User Name</label>
        <input type="text" id="username" name="username" ngModel class="form-control" placeholder="User Name" required autofocus>
        <br/>
        <label for="email" class="sr-only">Email</label>
        <input type="email" id="email" name="email" ngModel class="form-control" placeholder="Email" required>
        <br/>
        <label for="password" class="sr-only">Password</label>
        <input type="password" id="password" name="password" ngModel class="form-control" placeholder="Password" required>
        <br/>
        <button class="btn btn-lg btn-primary btn-block" type="submit">Register</button>
    </div>
</form>

We can create the Navigation Menu component now.

ng g component NavMenu

Copy the code below for component class file.

nav-menu.component.ts 

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { JwtHelperService } from '@auth0/angular-jwt';

@Component({
  selector: 'app-nav-menu',
  templateUrl: './nav-menu.component.html',
  styleUrls: ['./nav-menu.component.css']
})
export class NavMenuComponent implements OnInit {

  public jwtHelper: JwtHelperService = new JwtHelperService();

  public isExpanded = false;

  constructor(private router: Router) { }

  ngOnInit(): void {
  }

  collapse() {
    this.isExpanded = false;
  }

  toggle() {
    this.isExpanded = !this.isExpanded;
  }

  isUserAuthenticated() {
    const token: string | null = localStorage.getItem("accessToken");
    if (token && !this.jwtHelper.isTokenExpired(token)) {
      return true;
    }
    else {
      return false;
    }
  }

  public logOut = () => {
    localStorage.removeItem("accessToken");
    localStorage.removeItem("refreshToken");
    this.router.navigate(["login"]);
  }
}

Copy the code below for component HTML file.

nav-menu.component.html 

<header>
    <nav class='stroke navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3'>
      <div class="container">
        <a class="nav-link text-dark" style="color: black; font-weight: bold; font-size: large;"
          [routerLink]='["/"]'>Home</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse"
          aria-label="Toggle navigation" [attr.aria-expanded]="isExpanded" (click)="toggle()">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse" [ngClass]='{"show": isExpanded}'>
          <ul class="navbar-nav flex-grow">
            <li class="nav-item" [routerLinkActive]='["link-active"]'>
              <a class="nav-link text-dark" [routerLink]='["/addresses"]'>Address Book</a>
            </li>
            <li *ngIf="!isUserAuthenticated()" class="nav-item" [routerLinkActive]='["link-active"]'>
              <a class="nav-link text-dark" [routerLink]='["/register"]'>Register</a>
            </li>
            <li *ngIf="!isUserAuthenticated()" class="nav-item" [routerLinkActive]='["link-active"]'>
              <a class="nav-link text-dark" [routerLink]='["/login"]'>Login</a>
            </li>
            <li *ngIf="isUserAuthenticated()" class="logout" (click)="logOut()">
              <i class="fa fa-sign-out" aria-hidden="true">Logout</i>
            </li>
          </ul>
        </div>
      </div>
    </nav>
  </header>
  <footer>
    <nav class="navbar navbar-light bg-white mt-5 fixed-bottom">
      <div class="navbar-expand m-auto navbar-text">
        Developed with <i class="fa fa-heart"></i> by <b>Sarathlal
          Saseendran</b>
      </div>
    </nav>
  </footer>

Copy the code below for component styles.

nav-menu.component.css 

.box-shadow {
    box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}

/* NAVIGATION */
nav {
    width     : 100%;
    margin    : 0 auto;
    background: #fff;
    padding   : 10px 0;
    box-shadow: 0px 5px 0px #dedede;
}

nav ul {
    list-style: none;
    text-align: center;
}

nav ul li {
    display: inline-block;
}

nav ul li a {
    display        : block;
    padding        : 15px;
    text-decoration: none;
    color          : #aaa;
    font-weight    : 800;
    text-transform : uppercase;
    margin         : 0 10px;
}

nav ul li a,
nav ul li a:after,
nav ul li a:before {
    transition: all .5s;
}

nav ul li a:hover {
    color: #555;
}


/* stroke */
nav.stroke ul li a,
nav.fill ul li a {
    position: relative;
    font-size: 14px;
}

nav.stroke ul li a:after,
nav.fill ul li a:after {
    position  : absolute;
    bottom    : 0;
    left      : 0;
    right     : 0;
    margin    : auto;
    width     : 0%;
    content   : '.';
    color     : transparent;
    background: #aaa;
    height    : 1px;
}

nav.stroke ul li a:hover:after {
    width: 100%;
}


.fa-heart {
    color: hotpink;
}

We can create Home component now.

ng g component Home 

Copy the code below for component class file.

home.component.ts 

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}

Copy the code below for component HTML file.

home.component.html 

<div style="text-align:center;">
    <img src="../../assets/Angular 13_JWT Auth.png" width="750px" style="align-content: center;">
</div>

We can create our final component Addresses now.

ng g component Addresses 

Copy the code below for component class file.

addresses.component.ts 

import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { environment } from 'src/environments/environment';

@Component({
  selector: 'app-addresses',
  templateUrl: './addresses.component.html',
  styleUrls: ['./addresses.component.css']
})
export class AddressesComponent implements OnInit {

  public addresses!: Address[] ;

  constructor(private http: HttpClient) { }

  ngOnInit() {
    this.http.get(environment.baseUrl + "addresses", {
    }).subscribe({
      next: (response) => {
        this.addresses = response as Address[];
      },
      error: (err) => {
        console.error(err)
      },
      complete: () => console.info('Address complete')
    });
  }

}

export interface Address {
  name: string;
  houseName: string;
  city: string;
  state: string;
  pin: number;
}

Copy the code below for component HTML file.

addresses.component.html 

<h1>My Address Book</h1>
<div *ngFor="let address of addresses; let i = index">
    <div *ngIf="i % 2 == 0" class="row mt-3">
        <div class="col-sm-6" *ngIf="i  < addresses.length">
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">{{addresses[i].name}}</h5>
                    <p class="card-text">{{addresses[i].houseName}}</p>
                    <p class="card-text">{{addresses[i].city}}, {{addresses[i].state}}</p>
                    <a class="btn btn-primary">Pin - {{addresses[i].pin}}</a>
                </div>
            </div>
        </div>
        <div class="col-sm-6" *ngIf="i + 1 < addresses.length">
            <div class="card">
                <div class="card-body">
                    <h5 class="card-title">{{addresses[i+1].name}}</h5>
                    <p class="card-text">{{addresses[i+1].houseName}}</p>
                    <p class="card-text">{{addresses[i+1].city}}, {{addresses[i].state}}</p>
                    <a class="btn btn-primary">Pin - {{addresses[i+1].pin}}</a>
                </div>
            </div>
        </div>
    </div>
</div>

We can modify AppModule with the code changes below.

app.module.ts 

import { NgModule } from '@angular/core';

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { ToastrModule } from 'ngx-toastr';
import { AppRoutingModule } from './app-routing.module';

import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { HomeComponent } from './home/home.component';
import { AddressesComponent } from './addresses/addresses.component';

import { AuthGuard } from './guard/auth-guard.service';
import { MyInterceptor } from './my-interceptor';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    RegisterComponent,
    NavMenuComponent,
    HomeComponent,
    AddressesComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    FormsModule,
    HttpClientModule,
    ToastrModule.forRoot(),
    AppRoutingModule
  ],
  providers: [
    AuthGuard,
    { provide: HTTP_INTERCEPTORS, useClass: MyInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

We can add the routes below to the AppRoutingModule.

app-routing.module.ts 

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AddressesComponent } from './addresses/addresses.component';
import { AuthGuard } from './guard/auth-guard.service';
import { HomeComponent } from './home/home.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'login', component: LoginComponent },
  { path: 'register', component: RegisterComponent },
  { path: 'addresses', component: AddressesComponent, canActivate: [AuthGuard] }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

We can change the AppComponent HTML file with changes below.

app.component.html 

<body>    
  <app-nav-menu></app-nav-menu>    
  <div class="container">    
    <router-outlet></router-outlet>    
  </div>    
</body> 

This is particularly important for showing the different routes in the application.

We can add code changes below to style.css

style.css 

@import "~bootstrap/dist/css/bootstrap.css";
@import "~font-awesome/css/font-awesome.css";  

#toast-container > div {
    opacity:1;
}

body{
    padding: 20px;
}

.form-control.ng-touched.ng-invalid{
    border: 2px solid red;
}

.logout:hover{
    cursor: pointer;
}

We have imported bootstrap and font-awesome stylesheets in the above class file. So that, it can be used entire application without further individual importing.

We can run the .NET 6.0 and Angular applications now.

Currently, we have no registered users available in the database. We can register a new user.

After successful user registration, we can login to the application using the same credentials.

We can access the Address book now.

We have hard coded three address book entries in the backend application.

We have currently set one minute for access token expiry. Hence after the token expiry, Auth guard will refresh the token automatically.

We can try to access the address book after the expiry of earlier access token and see what happens.

We can see that the Auth guard will automatically renew the access token and refresh token and set the new values into the local storage. For testing purposes, I have just added a toaster message after each token renewal.

JWT Authentication with refresh token .NET 6.0 backend application related article and source code can be downloaded from the link below.

JWT Authentication With Refresh Tokens In .NET 6.0

Conclusion

In this article, we have seen the client-side implementation of JWT refresh token. We have already seen how to create .NET 6.0 backend application for JWT token refresh in the earlier article. Hopefully, you may get full understanding of JWT token authentication with refresh tokens in these two articles. Please feel free to give your valuable comments about my articles.


Similar Articles