Angular - Logout when Token is expired
Tags: angular, webdev, javascript, todayilearned
Published: 2022-09-18 02:01:41

In this Angular 14 tutorial, I will show you how to logout when JWT Token is expired. You also know two approaches to checking if JWT token is expired or not in Angular.

This tutorial is from BezKoder:
[https://www.bezkoder.com/logout-when-token-expired-angular-14/](https://www.bezkoder.com/logout-when-token-expired-angular-14/)

<h2>Check if JWT token is expired or not in Angular</h2>
There are two ways to check if Token is expired or not.
<ul>
	<li>1. Proactive strategy: get expiry time in JWT and compare with current time</li>
	<li>2. Reactive strategy: read response status from the server</li>
</ul>

I will show you the implementations of both approaches.
– For 1, we check the token expiration and call logout method/dispatch logout event.
– For 2, we dispatch logout event to App component when response status tells us the token is expired.

We're gonna use the code base for next steps. So you may need to read following tutorial first:
<a href="https://www.bezkoder.com/angular-14-jwt-auth/">Angular 14 JWT Authentication & Authorization example</a>

The Github source code is at the end of the tutorial.

<h2>Check the token expiration in Angular</h2>
With this approach, we get expiry time from JWT token (stored in Browser Local Storage or Session Storage) and compare with the current time.

```js
private isTokenExpired(token: string) {
  const expiry = (JSON.parse(atob(token.split('.')[1]))).exp;
  return expiry * 1000 > Date.now();
}

ngOnInit() {
  if (this.isTokenExpired(token)) {
    // call logout method/dispatch logout event
  } else {
    // token is valid: send requests...
  }
}
```

For more details about structure of a JWT, kindly visit:
<a href="https://www.bezkoder.com/jwt-json-web-token/">In-depth Introduction to JWT-JSON Web Token</a>

How about the Token is stored in <strong>HttpOnly Cookie</strong> that can't be accessed by JavaScript?
On client side, we don't check the JWT as cookie on server side, so we will use Interceptor to catch the HTTP request/response from token expiring, then send token refresh call and replay the HTTP request.

Token Refresh endpoint is implemented on the server side and it is a little bit complicated. For instructions:
- <a href="https://www.bezkoder.com/spring-security-refresh-token/">Spring Boot Refresh Token with JWT</a>
- <a href="https://www.bezkoder.com/jwt-refresh-token-node-js/">Node.js Refresh Token with JWT</a>
- <a href="https://www.bezkoder.com/jwt-refresh-token-node-js-mongodb/">Node.js Refresh Token with JWT and MongoDB</a>

In this post, we just logout when Token is expired.
For Refresh Token, please visit:
[Angular Refresh Token with JWT example](https://www.bezkoder.com/angular-14-refresh-token/)

<h2>Logout when Token is expired in Angular</h2>
Typically you don't check token validity on the client side (Angular) but catch the <strong>401</strong> response in the Interceptor. We will handle JWT token expiration using an <code>HTTP_INTERCEPTOR</code> provider. With the Interceptor, we can add the Bearer token to HTTP requests or handle errors.

We will dispatch <code>logout</code> event to <code>App</code> component when response status tells us the access token is expired.

First we need to set up a global event-driven system, or a PubSub system, which allows us to listen and dispatch (emit) events from independent components so that they don't have direct dependencies between each other.

We're gonna create <code>EventBusService</code> with two methods: <code>on</code>, and <code>emit</code>.

<strong>_shared</strong>/<em>event-bus.service.ts</em>
```js
import { Injectable } from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { EventData } from './event.class';

@Injectable({
  providedIn: 'root'
})
export class EventBusService {
  private subject$ = new Subject&lt;EventData>();

  constructor() { }

  emit(event: EventData) {
    this.subject$.next(event);
  }

  on(eventName: string, action: any): Subscription {
    return this.subject$.pipe(
      filter((e: EventData) => e.name === eventName),
      map((e: EventData) => e["value"])).subscribe(action);
  }
}
```

<strong>_shared</strong>/<em>event.class.ts</em>
```js
export class EventData {
  name: string;
  value: any;

  constructor(name: string, value: any) {
    this.name = name;
    this.value = value;
  }
}
```

Now you can emit <code>event</code> to the bus and if any listener was registered with the <code>eventName</code>, it will execute the callback function <code>action</code>.

Next we import <code>EventBusService</code> in App component and listen to <code>"logout"</code> event.

<strong>src</strong>/<em>app.component.ts</em>
```js
import { Component } from '@angular/core';
import { Subscription } from 'rxjs';
import { StorageService } from './_services/storage.service';
import { AuthService } from './_services/auth.service';
import { EventBusService } from './_shared/event-bus.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  // ...

  eventBusSub?: Subscription;

  constructor(
    private storageService: StorageService,
    private authService: AuthService,
    private eventBusService: EventBusService
  ) {}

  ngOnInit(): void {
    // ...

    this.eventBusSub = this.eventBusService.on('logout', () => {
      this.logout();
    });
  }

  logout(): void {
    this.authService.logout().subscribe({
      next: res => {
        console.log(res);
        this.storageService.clean();

        window.location.reload();
      },
      error: err => {
        console.log(err);
      }
    });
  }
}
```

Finally we only need to emit <code>"logout"</code> event in the Angular Http Interceptor.

```js
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HTTP_INTERCEPTORS, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

import { StorageService } from '../_services/storage.service';
import { EventBusService } from '../_shared/event-bus.service';
import { EventData } from '../_shared/event.class';

@Injectable()
export class HttpRequestInterceptor implements HttpInterceptor {
  private isRefreshing = false;

  constructor(private storageService: StorageService, private eventBusService: EventBusService) { }

  intercept(req: HttpRequest&lt;any>, next: HttpHandler): Observable&lt;HttpEvent&lt;any>> {
    req = req.clone({
      withCredentials: true,
    });

    return next.handle(req).pipe(
      catchError((error) => {
        if (
          error instanceof HttpErrorResponse &&
          !req.url.includes('auth/signin') &&
          error.status === 401
        ) {
          return this.handle401Error(req, next);
        }

        return throwError(() => error);
      })
    );
  }

  private handle401Error(request: HttpRequest&lt;any>, next: HttpHandler) {
    if (!this.isRefreshing) {
      this.isRefreshing = true;

      if (this.storageService.isLoggedIn()) {
        this.eventBusService.emit(new EventData('logout', null));
      }
    }

    return next.handle(request);
  }
}

export const httpInterceptorProviders = [
  { provide: HTTP_INTERCEPTORS, useClass: HttpRequestInterceptor, multi: true },
];
```

In the code above, we:
- intercept requests or responses before they are handled by <code>intercept()</code> method.
- handle <code>401</code> error status on interceptor response (except response of <code>/signin</code> request)
- emit <code>"logout"</code> event if user is logged in.

<h2>Logout when JWT Token is expired without Interceptor</h2>
This is another way to logout the user when Token is expired. This approach is not recommended because we have to catch <code>401</code> error status in every Http Request that accesses protected resources.

```js
import { Component, OnInit } from '@angular/core';
import { UserService } from '../_services/user.service';
import { StorageService } from '../_services/storage.service';
import { EventBusService } from '../_shared/event-bus.service';
import { EventData } from '../_shared/event.class';

@Component({
  selector: 'app-board-user',
  templateUrl: './board-user.component.html',
  styleUrls: ['./board-user.component.css']
})
export class BoardUserComponent implements OnInit {
  content?: string;

  constructor(
    private userService: UserService,
    private storageService: StorageService,
    private eventBusService: EventBusService
  ) {}

  ngOnInit(): void {
    this.userService.getUserBoard().subscribe({
      next: data => {
        this.content = data;
      },
      error: err => {
        // ...

        if (err.status === 401 && this.storageService.isLoggedIn()) {
          this.eventBusService.emit(new EventData('logout', null));
        }
      }
    });
  }
}
```

<h2>Conclusion</h2>
Proactive token strategy and the Reactive token strategy have their own pros and cons.

<strong>401</strong> will always be handled correctly in Reactive strategy, but before knowing the token is expired or not, it requires response from HTTP Request. If we provide a 10+ minute (or more) expiration period, the API calls aren't likely to really impose a burden on user.

Proactive strategy reduces the HTTP Request, because the token could expire before the request, but it adds overhead to each request.

Some people could validate the token before sending some HTTP requests, but also include 401 replay logic.

For Refresh Token:
[Angular Refresh Token with JWT example](https://www.bezkoder.com/angular-14-refresh-token/)

<h2>Source Code</h2>
You can find the complete source code for this tutorial on <a href="https://github.com/bezkoder/angular-14-jwt-auth">Github</a>.

<h2>Further Reading</h2>
<ul>
	<li><a href="https://www.bezkoder.com/jwt-json-web-token/">In-depth Introduction to JWT-JSON Web Token</a></li>
	<li><a href="https://www.bezkoder.com/angular-14-jwt-auth/">Angular 14 JWT Authentication & Authorization example</a></li>
	<li><a href="https://www.bezkoder.com/angular-14-form-validation/">Angular 14 Form Validation example</a></li>
	<li><a href="https://www.bezkoder.com/angular-14-crud-example/">Angular 14 CRUD example with Web API</a></li>
	<li><a href="https://www.bezkoder.com/angular-14-file-upload/">Angular 14 File upload example</a></li>
	<li><a href="https://www.bezkoder.com/angular-14-pagination-ngx/">Angular 14 Pagination example (server side)</a></li>
</ul>

Fullstack:
<ul>
	<li><a href="https://www.bezkoder.com/angular-14-spring-boot-jwt-auth/">Angular 14 + Spring Boot: JWT Authentication & Authorization example</a></li>
	<li><a href="https://www.bezkoder.com/node-js-angular-14-jwt-auth/">Angular 14 + Node.js Express: JWT Authentication & Authorization example</a></li>
</ul>