I have implemented refreshtoken logic using interceptor by referring this link Reference Link. It is working fine as expected. But in this case I am facing one critical issue that when I call more than one api parallelly and all api returns 401 then refreshtoken call more then one time depending upon how fast this.isRefreshing variable value set to true. Now my question is that I want to call refreshtoken api only for one time and all other pending api (which were already returned 401) must be call. How to achieve this?
There are 3 api calls. All returns 401 so all will trying call refreshtoken. Only one refreshtoken api should be call and all other red coloured api should be served one by one or parallelly.
Following is my interceptor.ts.
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Injectable, Injector } from "@angular/core";
import { BehaviorSubject, Observable, throwError } from "rxjs";
import { catchError, filter, switchMap, take } from "rxjs/operators";
import { AuthenticationService, CommonService } from "../services";
import { TokenService } from "../services/token.service";
/**
* Link Refere : https://stackoverflow.com/questions/57637923/angular-8-intercept-call-to-refresh-token
*/
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private isRefreshing = false;
private tokenService;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
/* No Direct Service inject on Constructor
Using Like:
const authService = this.injector.get(AuthenticationService);
*/
constructor(private injector: Injector) {
this.tokenService = this.injector.get(TokenService);
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
//const token = localStorage.getItem('accessToken');
// const token = this.tokenService.accessToken;
// if (token) {
// req = this.addTokenHeader(req, token);
// }
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);
// }
if (error instanceof HttpErrorResponse && error.status === 401) {
return this.handle401Error(req, next);
}
return throwError(error);
}));
}
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
console.log("401 error request:", request);
const authService = this.injector.get(AuthenticationService);
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
return authService.refreshToken().pipe(
switchMap((token: any) => {
this.isRefreshing = false;
this.tokenService.accessToken = token.accessToken;
this.refreshTokenSubject.next(token.accessToken);
return next.handle(request);
//return next.handle(this.addTokenHeader(request, token.accessToken));
}),
catchError((err) => {
this.isRefreshing = false;
authService.directRedirectToLogin();
// authService.logout().subscribe(data => {
// authService.redirectToLogin();
// });
return throwError(err);
})
);
}
return this.refreshTokenSubject.pipe(
filter(token => token !== null),
take(1),
switchMap((token) => next.handle(request))
//switchMap((token) => next.handle(this.addTokenHeader(request, token)))
);
}
private addTokenHeader(request: HttpRequest<any>, token: string) {
const setHeaders = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
if (request.body instanceof FormData) {
delete setHeaders["Content-Type"];
}
return request.clone({
setHeaders: setHeaders
});
}
}