angular http interceptor not calling request again after token refresh

Tags: , , , ,



I have a http interceptor in my project, it handles the refreshing of the access token.
When a user’s access token expires the request will get a 401 error, in that case, this function should handle everything, refreshing the token and calling the request again, with the new access token.

Here is the calling of the function:

return next.handle(request).pipe(catchError((error) => {
  if (error instanceof HttpErrorResponse && error.status === 401) {
    return this.handle401Error(request, next);
  } else {
    return throwError(error);
  }
}));

And the handle401:

  handle401Error(request: HttpRequest<any>, next: HttpHandler): any {
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      this.refreshTokenSubject.next(null);

      this.auth.refreshAccessToken().then((token: Token) => {
        this.isRefreshing = false;
        this.refreshTokenSubject.next(token.access_token);
        return next.handle(this.addToken(request, token.access_token));
      });
    } else {
      return this.refreshTokenSubject.pipe(
          filter((token) => token !== null),
          take(1),
          switchMap((token) => {
            return next.handle(this.addToken(request, token));
          }));
    }
  }

I created the interceptor from an article, which should work fine, the token refreshing works like a charm, but the

return next.handle(this.addToken(request, token.access_token));

Which should call the request again with the now valid token just doesn’t call it.

Answer

The problem

this.auth.refreshAccessToken() returns a promise (I assume given the .then()).

Explanation

Just in case you are not familiar with promises, they are a common system for handling asynchronous code. Here is a link to the docs.

The this.auth.refreshAccessToken().then() takes a function as an argument, as is common, you have provided an anonymous arrow function (token: Token) => { ... }.

When you do return next.handle(this.addToken(request, token.access_token));, you are inside the arrow function, so you are not actually returning a value from handle401Error(), you are returning a value to .then().

.then() does return a value, but you aren’t returning that at the moment.

You can see this done correctly in your else block:

return this.refreshTokenSubject.pipe(                          <-- top-level return
          filter((token) => token !== null),
          take(1),
          switchMap((token) => {
            return next.handle(this.addToken(request, token)); <-- nested return
          }));
    }

The solution

TLDR;

 return from(this.auth.refreshAccessToken()).pipe(switchMap((token: Token) => {
        this.isRefreshing = false;
        this.refreshTokenSubject.next(token.access_token);
        return next.handle(this.addToken(request, token.access_token));
      }));

Explanation

A small thing that might make things easier, I would recommend instead of any as the return type of handle401Error() you use the return type of handle.next() which is Observable<HttpEvent<any>>.

What you need to do is return the value of next.handle() from inside this.auth.refreshAccessToken().then().

There are probably multiple ways to do this, but I’m going to recommend the Angular/RxJS style.

As I said before, promises are like observables and RxJS (v6+) provides a way to convert a promise to an observable, for example:

import { from } from 'rxjs';
const observable = from(promise);

You can use this to convert this.auth.refreshAccessToken() to an observable:

from(this.auth.refreshAccessToken())

Now we have an observable, you might be inclined to get the value out using subscribe but that is not what you want to do because your interceptor is returning a final observable that gets subscribed to elsewhere.

What you can do instead is use pipe, which allows you to use a number of operators provided by RxJS. In this case, you want to wait for your first observable refreshAccessToken() to emit and then you want to return next.handle(). The commonly used operator for this task is switchMap.

You will notice that your else block is actually using this:

return this.refreshTokenSubject.pipe(
          filter((token) => token !== null),
          take(1),
          switchMap((token) => {                                <-- switchMap
            return next.handle(this.addToken(request, token));
          }));
    }

switchMap() waits for the first observable to emit, and then outputs the value into your callback function, expecting you to return another observable. In you case, this would mean that your replace then() with pipe(switchMap()).

As shown in the TLDR:

 return from(this.auth.refreshAccessToken()).pipe(switchMap((token: Token) => {
        this.isRefreshing = false;
        this.refreshTokenSubject.next(token.access_token);
        return next.handle(this.addToken(request, token.access_token));
      }));

This should resolve your issue, please comment below if this doesn’t work.



Source: stackoverflow