'Angular Interceptor ignoring observable when refreshToken is invalid and not catching errors

This is my first question here on Stack Overflow, I've been bashing my head with this problem for a few days and can't seem to find anything related to what's happening with me.

I followed the answer from Andrei Ostrovski in this link: angular-4-interceptor-retry-requests-after-token-refresh

So I could make a interceptor to issue access and refresh tokens based on errors.

This is the relevant part of the code from my interceptor class:

@Injectable()
export class TokensInterceptor implements HttpInterceptor {
  refreshTokenInProgress = false;
  tokenRefreshedSource = new Subject<void>();
  tokenRefreshed$ = this.tokenRefreshedSource.asObservable();

  constructor(private loginService: LoginService, private router: Router) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
    return next.handle(request).pipe(
      catchError((error) => {
        return this.handleResponseError(error, request, next);
      })
    );
  }

  refreshToken(): Observable<any> {
    if (this.refreshTokenInProgress) {
      return new Observable((observer) => {
        this.tokenRefreshed$.subscribe(() => {
          observer.next();
          observer.complete();
        });
      });
    } else {
      this.refreshTokenInProgress = true;
      return this.loginService.refreshToken().pipe(
        tap(() => {
          this.refreshTokenInProgress = false;
          this.tokenRefreshedSource.next();
        }),
        catchError(() => {
          this.refreshTokenInProgress = false;
          return <any>this.logout();
        })
      );
    }
  }

  logout() {
    this.router.navigate(['/login']);
  }

  handleResponseError(error: any, request?: any, next?: any): Observable<any> {
    if (error.status === 400) {
      //ErrorMessage
    } else if (error.status === 401) {
      return this.refreshToken().pipe(
        switchMap(() => {
          request = request.clone();
          return next.handle(request);
        }),
        catchError((e) => {
          if (e.status !== 401) {
            return this.handleResponseError(e, request, next);
          } else {
            return <any>this.logout();
          }
        })
      );
    } else if (error.status === 403) {
      //ErrorMessage
    } else if (error.status === 500) {
      //ErrorMessage
    } else if (error.status === 503) {
      //ErrorMessage
    }
    return <any>this.logout();
  }
}

Here is the relevant code from my LoginService:

@Injectable({
  providedIn: 'root',
})
export class LoginService {
  constructor(private httpClient: HttpClient) {}

  refreshToken() {
    const httpOptions = {
      headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
      withCredentials: true,
      observe: 'response' as 'response',
    };
    var object = new Object();
    return this.httpClient.post<any>(
      'https://localhost:5001/api/Account/Refresh',
      object,
      httpOptions
    );
  }
}

It all works well when my access token is expired and my refresh token is still valid. My failed requests are caught by the interceptor, then I refresh both tokens with my still valid refresh token and rerun the requests. Great...

The problem is when my refresh token is expired. When I call this.refreshToken().pipe..., the call seems to be ignored, I tried to use a debugger to see what was happening, and this.refreshToken().pipe... was being skipped entirely, not catching any error, resulting in nothing being made, when it should return the user to the login screen.

I tested my back-end with swagger and the endpoint responds correctly when my refresh token is expired, returning 401 and a message

"The refresh token is expired"

Can someone enlighten me to what's happening in my interceptor? Why it is ignoring the refreshToken method when my refresh token is expired?

Thanks in advance!

EDIT:

Okay, I tested more and it seems that this.refreshToken().pipe... is being called indeed, but this.loginService.refreshToken().pipe... which is inside this.refreshToken() method is not doing anything, leaving the variable refreshTokenInProgress forever true (in progress).



Solution 1:[1]

Ok, I found the problem, now my code is working as intended, I had to change my backend. My endpoint to refresh the token was returning 401 unauthorized whenever the refresh token was expired or invalid, I just had to change from return Unauthorized("Refresh token expired"); to return Ok("Refresh token expired"); and insert return throwError(() => error); inside my catchError like that

catchError((error): any => {
  this.refreshTokenInProgress = false;
  return throwError(() => error);
)

I hope I can help someone else having this problem

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Augusto Santos