luca
luca

Reputation: 3318

Return remaked call after JWT token refresh

I'm using JWT and I have this logic:

  1. Make an http call
  2. Return 401 if the token is exipered or the result
  3. When return 401 I have to make an http call to require the new token
  4. Remake the initial call with the new token
  5. Return the result

Al this procedure has to be hidden to the user. I already catch the 401 status code and repeated the original call after retrieve the new token, the problem is to return the result to the original call. This is the service with the http request:

    getListCategories(){
        return this.http.get<Category[]>("/api/configuration/category").pipe(
          catchError(err =>  this.handleError.handleError(err, { severity: 'error', summary: 'Error retrieving the list of categories', life: 5000 }))
        );
    }

this is the error interceptor that make the refresh call and repeat the original call:

    export class ErrorInterceptorService implements HttpInterceptor {

      constructor(private auth: AuthService, public router: Router) { }

      intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const { shouldRetry } = this;
        return next.handle(request).pipe(
          retryWhen(genericRetryStrategy({
            shouldRetry
          })),

          catchError(err => {
            //401 the token is invalid so I have to refresh it
            if (err.status === 401 && request.url !== "/api/login") {
              this.auth.refreshToken().subscribe(
                (apiResult: SessionTokenResponse) => {
                  this.auth.saveToken(apiResult.token);
                  request = request.clone({ headers: request.headers.set('Authorization', 'Bearer ' + apiResult.token) });
                  next.handle(request).subscribe();
                },
              );
            } else if (err.status === 401 && request.url === "/api/login") {
              this.auth.logout()
            }else{
              const error = err.error.message || err.statusText;
              return throwError(error);
            }
          }),
        )
      }
      private shouldRetry = (error) => (error.error instanceof ErrorEvent);
    }

The problem is in the service, it doesn't wait the remake call but exit after the first error. Can you help me?Thanks

Upvotes: 0

Views: 111

Answers (1)

Antoniossss
Antoniossss

Reputation: 32507

You want to return Observable with chain operations that included extra request between original request and repeted one. I would try to do this using switchMap.

It would be something like this:

catchError(err => {
            if (err.status === 401 && request.url !== "/api/login") {
              //by returning observable here, you are "chaining" the calls so original caller will get this observable's result. `catchError` can be threated like `catch` block in `try-catch` where you can still do operations and return results - ergo continue operations.
              return this.auth.refreshToken()
                .switchMap( // this just switches from one observable to another
                (apiResult: SessionTokenResponse) => {
                  this.auth.saveToken(apiResult.token);
                  request = request.clone({ headers: request.headers.set('Authorization', 'Bearer ' + apiResult.token) });
                  return next.handle(request); // return original request handler with updated headers
                },
              );

Obviously not tested and might be syntax invalid as written here.

Upvotes: 1

Related Questions