Search code examples
angulartypescriptangular-http-interceptors

Angular HTTP Interceptor wait http requests until get a refresh token


I have built my AuthInterceptor which on a 401 error sends a request to get a new token.

The handle401Error method is called when I encounter a 401 error, But I am trying to wait other HTTP requests until I got the new token. But it's not waiting until to get the new refresh token although it is making an HTTP call to and getting a new access token. Please find the screenshot I have attached.

enter image description here

Interceptor.ts

isRefreshingToken = false;
tokenSubject: BehaviorSubject<string | null> = new BehaviorSubject<string | null>(null);

intercept(
    request: HttpRequest<any>,
    next: HttpHandler
): Observable<HttpEvent<any>> {

    const timeOut = appSettings.ajaxTimeout;
    const retryCount = appSettings.retryCount;
    const retryDelay = appSettings.retryDelayMS;

    return next.handle(request).pipe(
        timeout(timeOut),
        catchError((error) => {
            if (error instanceof HttpErrorResponse) {
                const httpErrorCode: number = error['status'];
                switch (httpErrorCode) {
                    case StatusCodes.BAD_REQUEST:
                        return throwError(error);
                        //return this.handle400Error(error);
                    case StatusCodes.UNAUTHORIZED:
                        return this.handle401Error(request, next);
                    default:
                        this._toastr.error(
                            'Sorry! something went wrong.',
                            'Error!'
                        );
                        return throwError(error);
                }
            } else {
                return throwError(error);
            }
            }),
            retryWhen((errors) => {
                return errors.pipe(
                    concatMap((error, count) => {
                        if (count < retryCount) {
                            return of(error);
                        }
                        return throwError(error);
                    }),
                    delay(retryDelay)
                );
            })
        );
    }

    handle401Error(
        request: HttpRequest<any>,
        next: HttpHandler
    ): Observable<HttpEvent<any>> {
        if (!this.isRefreshingToken) {
            this.isRefreshingToken = true;
            console.log('401');

            // Reset here so that the following requests wait until the token
            // comes back from the refreshToken call.
            this.tokenSubject.next(null);

            return this._spotify.getRefreshToken().pipe(
                switchMap((authData: ISpotifyTokens) => {
                    if (authData) {
                        console.log('new token success');
                        this.updateTokenInCookie(authData);
                        this.tokenSubject.next(authData.access_token);
                        return next.handle(request);
                    }
                    return this.logoutUser();
                }),
                catchError((error) => {
                    // If there is an exception calling 'refreshToken', bad news so logout.
                    return this.logoutUser();
                }),
                finalize(() => {
                    this.isRefreshingToken = false;
                })
            );
        } else {
            return next.handle(request);
        }
    }

    updateTokenInCookie(authData: ISpotifyTokens) {
        this._spotify.updateTokensInStorage(authData);
        this._spotify.startRefreshTokenTimer(authData.expires_in);
    }

    logoutUser() {
        this._spotify.clearTokensFromStorage();
        this._router.navigate(['/welcome']);
        return throwError('');
    }

Solution

  • I have solved the problem using the code below. If anyone has any better solution please suggest it.

    private isRefreshingToken = false;
    private timeOut = appSettings.ajaxTimeout;
    private tokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
    
    intercept(
        request: HttpRequest<any>,
        next: HttpHandler
    ): Observable<HttpEvent<any>> {
        return next.handle(request).pipe(
        timeout(this.timeOut),
        catchError((error) => {
            if (error instanceof HttpErrorResponse) {
            const httpErrorCode: number = error['status'];
            switch (httpErrorCode) {
                case StatusCodes.BAD_REQUEST:
                  return throwError(error);
                case StatusCodes.UNAUTHORIZED:
                  return this.handle401Error(request, next);
                default:
                  this._toastr.error(
                    'Sorry! something went wrong.',
                    'Error!'
                  );
                  return throwError(error);
               }
               } else {
                 return throwError(error);
               }
           })
        );
    }
    
    private handle401Error(
        request: HttpRequest<any>,
        next: HttpHandler
    ): Observable<HttpEvent<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);
                return this._spotify.getRefreshToken().pipe(
                    switchMap((response: ISpotifyTokens) => {
                        console.log('updating on 401');
                        // Updating new token in cookie
                        this._spotify.updateTokensInStorage(response, false);
                        this.tokenSubject.next(response.access_token);
                        return next.handle(
                            this.addTokenInHeader(request, response.access_token)
                        );
                    }),
                    catchError((error) => {
                        // If there is an exception calling 'refreshToken', bad news so logout.
                        this.logoutUser();
                        return throwError('');
                    }),
                    finalize(() => {
                        this.isRefreshingToken = false;
                    })
                );
            } else {
                return this.tokenSubject.pipe(
                    filter((token) => token != null),
                    take(1),
                    switchMap((token) => {
                        return next.handle(this.addTokenInHeader(request, token));
                    })
                );
            }
        }
    
    addTokenInHeader(
        request: HttpRequest<any>,
        token: string
    ): HttpRequest<any> {
        return request.clone({
            setHeaders: { Authorization: 'Bearer ' + token }
        });
    }
    
    logoutUser(): void {
        this._spotify.clearTokensFromStorage();
        this._router.navigate(['/welcome']);
    }