Trying to repeat a http request after refresh token with a interceptor in angular 7 Trying to repeat a http request after refresh token with a interceptor in angular 7 angular angular

Trying to repeat a http request after refresh token with a interceptor in angular 7


You have to distingiush among all the requests. For example you don't want to intercept your login request and also not the refresh token request. SwitchMap is your best friend because you need to cancel some calls to wait for your token is getting refreshed.

So what you do is check first for error responses with status 401 (unauthorized):

return next.handle(this.addToken(req, this.userService.getAccessToken()))            .pipe(catchError(err => {                if (err instanceof HttpErrorResponse) {                    // token is expired refresh and try again                    if (err.status === 401) {                        return this.handleUnauthorized(req, next);                    }                    // default error handler                    return this.handleError(err);                } else {                    return observableThrowError(err);                }            }));

In your handleUnauthorized function you have to refresh your token and also skip all further requests in the meantime:

  handleUnauthorized (req: HttpRequest<any>, next: HttpHandler): Observable<any> {        if (!this.isRefreshingToken) {            this.isRefreshingToken = true;            // Reset here so that the following requests wait until the token            // comes back from the refreshToken call.            this.tokenSubject.next(null);            // get a new token via userService.refreshToken            return this.userService.refreshToken()                .pipe(switchMap((newToken: string) => {                    // did we get a new token retry previous request                    if (newToken) {                        this.tokenSubject.next(newToken);                        return next.handle(this.addToken(req, newToken));                    }                    // If we don't get a new token, we are in trouble so logout.                    this.userService.doLogout();                    return observableThrowError('');                })                    , catchError(error => {                        // If there is an exception calling 'refreshToken', bad news so logout.                        this.userService.doLogout();                        return observableThrowError('');                    })                    , finalize(() => {                        this.isRefreshingToken = false;                    })                );        } else {            return this.tokenSubject                .pipe(                    filter(token => token != null)                    , take(1)                    , switchMap(token => {                        return next.handle(this.addToken(req, token));                    })                );        }    }

We have an attribute on the interceptor class which checks if there is already a refresh token request running: this.isRefreshingToken = true; because you don't want to have multiple refresh request when you fire multiple unauthorized requests.

So everthing within the if (!this.isRefreshingToken) part is about refreshing your token and try the previous request again.

Everything which is handled in else is for all requests, in the meantime while your userService is refreshing the token, a tokenSubject gets returned and when the token is ready with this.tokenSubject.next(newToken); every skipped request will be retried.

Here this article was the origin inspiration for the interceptor: https://www.intertech.com/angular-4-tutorial-handling-refresh-token-with-new-httpinterceptor/

EDIT:

TokenSubject is actually a Behavior Subject: tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);, which means any new subscriber will get the current value in the stream, which would be the old token from last time we call this.tokenSubject.next(newToken).

Withnext(null) every new subscriber does not trigger the switchMap part, thats why filter(token => token != null) is neccessary.

After this.tokenSubject.next(newToken) is called again with a new token every subscriber triggers the switchMap part with the fresh token. Hope it is more clearly now

EDIT 21.09.2020

Fix link


Below is the code for calling refresh token and after getting refresh token calls failed API's,

Comments in source code would help you to understand flow.Its tested and fine for below scenarios

1) If single request fails due to 401 then it will called for refresh token and will call failed API with updated token.

2) If multiple requests fails due to 401 then it will called for refresh token and will call failed API with updated token.

3) It will not call token API repeatedly

If still anyone found new scenario where this code not working please inform me so I will test and update it accordingly.

import { Injectable } from "@angular/core";import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse, HttpHeaders, HttpClient, HttpResponse } from "@angular/common/http";import { Observable } from "rxjs/Observable";import { throwError, BehaviorSubject } from 'rxjs';import { catchError, switchMap, tap, filter, take, finalize } from 'rxjs/operators';import { TOKENAPIURL } from 'src/environments/environment';import { SessionService } from '../services/session.service';import { AuthService } from '../services/auth.service';/** * @author Pravin P Patil * @version 1.0 * @description Interceptor for handling requests which giving 401 unauthorized and will call for  * refresh token and if token received successfully it will call failed (401) api again for maintaining the application momentum */@Injectable()export class RefreshTokenInterceptor implements HttpInterceptor {    private isRefreshing = false;    private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);    constructor(private http: HttpClient, private sessionService: SessionService, private authService: AuthService) { }    /**     *      * @param request HttpRequest     * @param next HttpHandler     * @description intercept method which calls every time before sending requst to server     */    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {        // Taking an access token        const accessToken = sessionStorage.getItem('ACCESS_TOKEN');        // cloing a request and adding Authorization header with token        request = this.addToken(request, accessToken);        // sending request to server and checking for error with status 401 unauthorized        return next.handle(request).pipe(            catchError(error => {                if (error instanceof HttpErrorResponse && error.status === 401) {                    // calling refresh token api and if got success extracting token from response and calling failed api due to 401                                        return this.handle401Error(request, next);                } // If api not throwing 401 but gives an error throwing error                else {                    return throwError(error);                }            }));    }    /**     *      * @param request HttpRequest<any>     * @param token token to in Authorization header     */    private addToken(request: HttpRequest<any>, token: string) {        return request.clone({            setHeaders: { 'Authorization': `Bearer ${token}` }        });    }    /**     * This method will called when any api fails due to 401 and calsl for refresh token     */    private handle401Error(request: HttpRequest<any>, next: HttpHandler) {        // If Refresh token api is not already in progress        if (this.isRefreshing) {            // If refreshTokenInProgress is true, we will wait until refreshTokenSubject has a non-null value            // – which means the new token is ready and we can retry the request again            return this.refreshTokenSubject                .pipe(                    filter(token => token != null),                    take(1),                    switchMap(jwt => {                        return next.handle(this.addToken(request, jwt))                    }));        } else {            // updating variable with api is in progress            this.isRefreshing = true;            // Set the refreshTokenSubject to null so that subsequent API calls will wait until the new token has been retrieved            this.refreshTokenSubject.next(null);            const refreshToken = sessionStorage.getItem('REFRESH_TOKEN');            // Token String for Refresh token OWIN Authentication            const tokenDataString = `refresh_token=${refreshToken}&grant_type=refresh_token`;            const httpOptions = {                headers: new HttpHeaders({                    'Content-Type': 'application/x-www-form-urlencoded',                    'X-Skip-Interceptor': ''                })            };            return this.http.post<any>(TOKENAPIURL, tokenDataString, httpOptions)                .pipe(switchMap((tokens) => {                    this.isRefreshing = false;                    this.refreshTokenSubject.next(tokens.access_token);                    // updating value of expires in variable                                        sessionStorage.setItem('ACCESS_TOKEN', tokens.access_token);                    return next.handle(this.addToken(request, tokens.access_token));                }));        }    }}


You can do something like this :

import { HttpErrorResponse } from '@angular/common/http';return next.handle(req).pipe(  catchError((err: any) => {    if (err instanceof HttpErrorResponse && err.status 401) {     return this._authenticationService.refresh()       .pipe(tap(         (success) => {},         (err) => {           this._authenticationService.logOut();           throw error;         }       ).mergeMap((res) => {         this._authenticationService.processLoginResponse(res);         newReq.headers.set("Authorization", "Bearer " + this._authenticationService.authResponse.token)         return next.handle(newReq)       });    } else {      return Observable.of({});    }  }));