pipe catchError not working while refreshing the token in interceptor

Viewed 758

It may be a stupid question to ask but i have created a interceptor where i catch if the response fails with code 401 and refresh token with an api. it is working fine but i also want to catch the error if for some reason the refresh token api also fails, here the code

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

    constructor(private authService: AuthService) { }

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

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any> | any> {

        return next.handle(this.addTokenToRequest(request, this.authService.getJWT()))
            .pipe(
                catchError(err => {
                    if (err instanceof HttpErrorResponse) {
                        switch ((<HttpErrorResponse>err).status) {
                            case 401:
                                return this.handle401Error(request, next);
                            case 400:
                                return <any>this.authService.logout();
                        }
                    } else {
                        return throwError(err);
                    }
                }));
    }

    private addTokenToRequest(request: HttpRequest<any>, token: string): HttpRequest<any> {
        return request.clone({ setHeaders: { Authorization: `Bearer ${token}` } });
    }

    private handle401Error(request: HttpRequest<any>, next: HttpHandler) {

        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.authService.refreshToken()
                .pipe(
                    switchMap((refreshToken: any) => {
                        console.log('here');
                        if (refreshToken) {
                            this.tokenSubject.next(refreshToken.token);
                            this.authService.setJWT(refreshToken.token);
                            this.authService.setRefreshToken(refreshToken.refreshToken);
                            // localStorage.setItem('currentUser', JSON.stringify(user));
                            return next.handle(this.addTokenToRequest(request, refreshToken.token));
                        }else{

                        }

                        return <any>this.authService.logout();
                    }),
                    catchError(err => {
                        console.log('here', err);
                        return <any>this.authService.logout();
                    }),
                    finalize(() => {
                        console.log('here');
                        this.isRefreshingToken = false;
                    })
                );
        } else {
            this.isRefreshingToken = false;

            return this.tokenSubject
                .pipe(filter(token => token != null),
                    take(1),
                    switchMap(token => {
                        return next.handle(this.addTokenToRequest(request, token));
                    }));
        }
    }
}

notice on the line where return this.authService.refreshToken() is called it does not reach to catchError if the refreshToken() fails

i understand that it may only reach to catchError when the original request will fail. so in the service i tried to catch the error from refresh token api, code below:

refreshToken(): Observable<any> {
    let jwt = this.getJWT();
    let refreshToken = this.getRefreshToken();

    debugger;
    return this.http.post<any>(this.api.REFRESH_TOKEN, { refreshToken: refreshToken, expiredToken: jwt })
      .pipe(
        map(result => {
          console.log('here');
          debugger;
          return result;
        }),
        catchError(e => {
          debugger;
          console.log(e, 'here');
          return null;
        })
      );
  }

it does not even reach to that point if the refresh token api fails, my question is to how do i catch the error from refresh token api and redirect user to login. Thanks.

1 Answers

You have to move your catchError block inside your switchMap, it's not a good error handling method to put catchError isn the first level of Observable pipe, if catchError is on the first level of the Observable pipe method, the finalize operator will be called

.pipe(switchMap((refreshToken: any) => {
      console.log('here');
      if (refreshToken) {
       this.tokenSubject.next(refreshToken.token);
       this.authService.setJWT(refreshToken.token);
       this.authService.setRefreshToken(refreshToken.refreshToken);
       // localStorage.setItem('currentUser', JSON.stringify(user));
       return next.handle(this.addTokenToRequest(request, refreshToken.token)).pipe(
           catchError(err => {
               console.log('here', err);
               return <any>this.authService.logout();
            })
       );
     }
     else{}

     return <any>this.authService.logout();
}),

 finalize(() => {
 console.log('here');
 this.isRefreshingToken = false;
})
Related