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/
Check if JWT token is expired or not in Angular
There are two ways to check if Token is expired or not.
- 1. Proactive strategy: get expiry time in JWT and compare with current time
- 2. Reactive strategy: read response status from the server
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:
Angular 14 JWT Authentication & Authorization example
The Github source code is at the end of the tutorial.
Check the token expiration in Angular
With this approach, we get expiry time from JWT token (stored in Browser Local Storage or Session Storage) and compare with the current time.
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:
In-depth Introduction to JWT-JSON Web Token
How about the Token is stored in HttpOnly Cookie 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:
In this post, we just logout when Token is expired.
I will write Angular tutorial for Refresh Token soon.
Logout when Token is expired in Angular
Typically you don’t check token validity on the client side (Angular) but catch the 401 response in the Interceptor. We will handle JWT token expiration using an HTTP_INTERCEPTOR
provider. With the Interceptor, we can add the Bearer token to HTTP requests or handle errors.
We will dispatch logout
event to App
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 EventBusService
with three methods: on
, and emit
.
_shared/event-bus.service.ts
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<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);
_shared/event.class.ts
export class EventData
name: string;
value: any;
constructor(name: string, value: any)
this.name = name;
this.value = value;
Now you can emit event
to the bus and if any listener was registered with the eventName
, it will execute the callback function action
.
Next we import EventBusService
in App component and listen to "logout"
event.
src/app.component.ts
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
private roles: string[] = [];
isLoggedIn = false;
showAdminBoard = false;
showModeratorBoard = false;
username?: string;
eventBusSub?: Subscription;
constructor(
private storageService: StorageService,
private authService: AuthService,
private eventBusService: EventBusService
)
ngOnInit(): void
this.isLoggedIn = this.storageService.isLoggedIn();
if (this.isLoggedIn)
const user = this.storageService.getUser();
this.roles = user.roles;
this.showAdminBoard = this.roles.includes('ROLE_ADMIN');
this.showModeratorBoard = this.roles.includes('ROLE_MODERATOR');
this.username = user.username;
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 "logout"
event in the Angular Http Interceptor.
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<any>, next: HttpHandler): Observable<HttpEvent<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<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
intercept()
method. - handle
401
error status on interceptor response (except response of/signin
request) - emit
"logout"
event if user is logged in.
Logout when JWT Token is expired without Interceptor
This is another way to logout the user when Token is expired. This approach is not recommended because we have to catch 401
error status in every Http Request that accesses protected resources.
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.error)
try
const res = JSON.parse(err.error);
this.content = res.message;
catch
this.content = `Error with status: $err.status - $err.statusText`;
else
this.content = `Error with status: $err.status`;
if (err.status === 401 && this.storageService.isLoggedIn())
this.eventBusService.emit(new EventData('logout', null));
});
}
}
Conclusion
Proactive token strategy and the Reactive token strategy have their own pros and cons.
401 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.
Source Code
You can find the complete source code for this tutorial on Github.
Further Reading
Fullstack: