1

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).

Vidya Sagar
  • 1,699
  • 3
  • 17
  • 28
  • What do mean when you say that `this.refreshToken().pipe...` is being ignored completely?. Is it skipping the else if 401 block? Is it not making the refresh HTTP request? Is it not reacting the way it should to that request? Could you please elaborate a bit where the issue is? – akotech Feb 16 '22 at 13:49
  • It is entering the first else if 401 block of handleResponseError method, so it understands that the request was not authorized, but when it reaches `this.refreshToken().pipe...`, it does nothing, I expected the refreshToken method to return 401 so my catchError could either loop to try and get another refresh token or logout the user. I even tried putting _console.log()_ or _tap_ inside `this.refreshToken().pipe...` to see if it the code was reached, but nothing... – Augusto Santos Feb 16 '22 at 14:02

1 Answers1

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