My final solution. Works with parallel requests.
UPDATE: The code updated with Angular 9 / RxJS 6, error handling and fix looping when refreshToken fails
import { HttpRequest, HttpHandler, HttpInterceptor, HTTP_INTERCEPTORS } from "@angular/common/http";
import { Injector } from "@angular/core";
import { Router } from "@angular/router";
import { Subject, Observable, throwError } from "rxjs";
import { catchError, switchMap, tap} from "rxjs/operators";
import { AuthService } from "./auth.service";
export class AuthInterceptor implements HttpInterceptor {
authService;
refreshTokenInProgress = false;
tokenRefreshedSource = new Subject();
tokenRefreshed$ = this.tokenRefreshedSource.asObservable();
constructor(private injector: Injector, private router: Router) {}
addAuthHeader(request) {
const authHeader = this.authService.getAuthorizationHeader();
if (authHeader) {
return request.clone({
setHeaders: {
"Authorization": authHeader
}
});
}
return request;
}
refreshToken(): Observable<any> {
if (this.refreshTokenInProgress) {
return new Observable(observer => {
this.tokenRefreshed$.subscribe(() => {
observer.next();
observer.complete();
});
});
} else {
this.refreshTokenInProgress = true;
return this.authService.refreshToken().pipe(
tap(() => {
this.refreshTokenInProgress = false;
this.tokenRefreshedSource.next();
}),
catchError(() => {
this.refreshTokenInProgress = false;
this.logout();
}));
}
}
logout() {
this.authService.logout();
this.router.navigate(["login"]);
}
handleResponseError(error, request?, next?) {
// Business error
if (error.status === 400) {
// Show message
}
// Invalid token error
else if (error.status === 401) {
return this.refreshToken().pipe(
switchMap(() => {
request = this.addAuthHeader(request);
return next.handle(request);
}),
catchError(e => {
if (e.status !== 401) {
return this.handleResponseError(e);
} else {
this.logout();
}
}));
}
// Access denied error
else if (error.status === 403) {
// Show message
// Logout
this.logout();
}
// Server error
else if (error.status === 500) {
// Show message
}
// Maintenance error
else if (error.status === 503) {
// Show message
// Redirect to the maintenance page
}
return throwError(error);
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
this.authService = this.injector.get(AuthService);
// Handle request
request = this.addAuthHeader(request);
// Handle response
return next.handle(request).pipe(catchError(error => {
return this.handleResponseError(error, request, next);
}));
}
}
export const AuthInterceptorProvider = {
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
};