最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - angular http interceptor not calling request again after token refresh - Stack Overflow

programmeradmin4浏览0评论

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.

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.

Share Improve this question asked Apr 1, 2021 at 18:14 Máté AntalMáté Antal 1671 silver badge25 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 8 +50

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 mon 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 mon, 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 remend 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 remend 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 monly 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 ment below if this doesn't work.

The handle handle401Error function shouldn't return next.handle(...). You already have that covered being returned in the intercept function anyways. Instead add the token to the request like normal and return of(null); in the handle401Error function. The catchError function should always return null unless you want to throw a custom error.

发布评论

评论列表(0)

  1. 暂无评论