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:
- Spring Boot Refresh Token with JWT
- Node.js Refresh Token with JWT
- Node.js Refresh Token with JWT and MongoDB
In this post, we just logout when Token is expired.
For Refresh Token, please visit:
Angular Refresh Token with JWT example
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 two 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 {
// ...
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 "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.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.
For Refresh Token:
Angular Refresh Token with JWT example
Source Code
You can find the complete source code for this tutorial on Github.
Further Reading
- In-depth Introduction to JWT-JSON Web Token
- Angular 14 JWT Authentication & Authorization example
- Angular 14 Form Validation example
- Angular 14 CRUD example with Web API
- Angular 14 File upload example
- Angular 14 Pagination example (server side)
Fullstack:
Top comments (4)
Pretty cool; thanks for sharing.
What do you think are the advantages of using a class for the EventData -- it seems for me that a type is enough. Thanks for your help
✌️
Hi, because we don't use inheritance or class method here, so I think that a type is enough, too. :)
P/s: Using class is just my habit for future use. Maybe I will need OOP principles on these objects :D
Cảm ơn bạn. Bình thường tôi dụng class vời DI của Angular thôi. Not sure if that's a smart move though.
What about a third option? The token usually contains the expiry date. So what we do is set a timer on that time - 10s and depending on the user wants to stay logged in, refreshing the token oder logging out to show the login screen again.