3

I'm making JWT interceptor to handle 401 error. My idea is collect all original requests after 401 to array, then refresh my token, then add a new header to to my request. Here is the code:

type CallerRequest = {
    subscriber: Subscriber<any>;
    failedRequest: HttpRequest<any>;
};

@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
  private refreshInProgress: boolean;
  private requests: CallerRequest[] = [];

  constructor(
    public authService: AuthService,
    public customersService: CustomersService,
    private http: HttpClient
    ) {
      this.refreshInProgress = false;
    }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (!req.url.includes('api/')) {
      return next.handle(req);
    }

  const observable = new Observable<HttpEvent<any>>((subscriber) => {
  const originalRequestSubscription = next.handle(req)
    .subscribe((response) => {
      subscriber.next(response);
    },
    (err) => {
      if (err.status === 401) {
        this.handleUnauthorizedError(subscriber, req);
      } else {
        subscriber.error(err);
      }
    },
    () => {
      subscriber.complete();
    });

    return () => {
      originalRequestSubscription.unsubscribe();
    };
  });
  return observable;
  }

  private addToken(request: HttpRequest<any>, token: string) {
    return request.clone({setHeaders: {'Authorization': `Bearer ${token}`}});
  }

  private handleUnauthorizedError(subscriber: Subscriber<any>, request: HttpRequest<any>) {
    this.requests.push({ subscriber, failedRequest: request });
    if (!this.refreshInProgress) {
      this.refreshInProgress = true;
      console.log('go refresh');
      this.authService.refreshToken()
        .pipe(
          finalize(() => {
            this.refreshInProgress = false;
          })
        )
        .subscribe(
          result => {
            console.log(result); // <-- I cannot get here
            this.repeatFailedRequests(this.authService.getAccessToken());

          },
        err => {
          console.log(err); // <-- And cannot get here
        }
      )

    }
  }

  private repeatFailedRequests(authHeader: string) {
    this.requests.forEach((c) => {
      const requestWithNewToken = this.addToken(c.failedRequest, authHeader);
      this.repeatRequest(requestWithNewToken, c.subscriber);
    });
    this.requests = [];
  }

  private repeatRequest(requestWithNewToken: HttpRequest<any>, subscriber: Subscriber<any>) {
    this.http.request(requestWithNewToken).subscribe((res) => {
      subscriber.next(res);
    },
      (err) => {
        if (err.status === 401) {
          this.authService.removeTokens();
        }
        subscriber.error(err);
      },
      () => {
        subscriber.complete();
      });
  }


}

Look at my handleUnauthorizedError method. I can't get results there.
By the way here is my refreshToken:

  refreshToken() {
    return this.post('api/v0/jwt/refresh/', {refresh: this.getRefreshToken()})
    .pipe(tap((tokens: Tokens) => {
      this.storeTokens(tokens);
    }));

}

What am I doing wrong and how would I solve this issue in a proper way? Thank you!

ADDED:

If I try JWT interceptor from this question Angular 4 Interceptor retry requests after token refresh (most popular answer) I get the same problem - my results from RefreshToken() are not handled.

Dmitry
  • 117
  • 1
  • 9
  • Does this answer your question? [Angular 4 Interceptor retry requests after token refresh](https://stackoverflow.com/questions/45202208/angular-4-interceptor-retry-requests-after-token-refresh) – Józef Podlecki May 28 '20 at 10:42
  • @JózefPodlecki same error. I can't catch error or result from my refreshToken() – Dmitry May 28 '20 at 11:04

2 Answers2

1

The problem was in proper handling JWT refresh requests while refreshInProgress. Here is the code which works just perfect:

type CallerRequest = {
    subscriber: Subscriber<any>;
    failedRequest: HttpRequest<any>;
};

@Injectable()
export class AuthTokenInterceptor implements HttpInterceptor {
  private refreshInProgress: boolean;
  private requests: CallerRequest[] = [];
  private refreshJWTReuest: Subscription;
  private straightRequests: string[];

  constructor(
    public authService: AuthService,
    public customersService: CustomersService,
    private http: HttpClient
    ) {
        this.refreshInProgress = false;
        this.straightRequests = ['currency/'];
      }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (this.straightRequests.some(v => req.url.includes(v))) {
      return next.handle(req);
    }

    if (req.url.includes('api/v0/jwt/create/')) {
      this.refreshInProgress = false;
    }


  const observable = new Observable<HttpEvent<any>>((subscriber) => {
  const originalRequestSubscription = next.handle(this.addToken(req, this.authService.getAccessToken()))
    .subscribe((response) => {
      subscriber.next(response);
    },
    (err) => {
      if (err.status === 401) {
        if (req.url.includes('api/v0/jwt/refresh/')) {
          this.refreshJWTReuest.unsubscribe();
          this.requests = [];
          this.authService.logout();
        }
        this.handleUnauthorizedError(subscriber, req);
      } else {
        // notification!!!
        subscriber.error(err);
      }
    },
    () => {
      subscriber.complete();
    });

    return () => {
      originalRequestSubscription.unsubscribe();
    };
  });
  return observable;
  }

  private addToken(request: HttpRequest<any>, token: string) {
    return request.clone({setHeaders: {'Authorization': `Bearer ${token}`}});
  }

  private handleUnauthorizedError(subscriber: Subscriber<any>, request: HttpRequest<any>) {
    if (!this.refreshInProgress) {
      this.refreshInProgress = true;
      this.refreshJWTReuest = this.authService.refreshToken()
        .subscribe(
          result => {
            this.refreshInProgress = false;
            this.repeatFailedRequests(result.access);
          },
        err => {
          this.authService.logout();
          console.log(err);
        }
      );

    }
    if (!request.url.includes('api/v0/jwt/refresh/')) {
      // avoid refresh requests while refreshInProgress
      // this solves a small bug after logout()
      this.requests.push({ subscriber, failedRequest: request });
    }

  }

  private repeatFailedRequests(authHeader: string) {
    this.requests.forEach((c) => {
      const requestWithNewToken = this.addToken(c.failedRequest, authHeader);
      this.repeatRequest(requestWithNewToken, c.subscriber);
    });
    this.refreshJWTReuest.unsubscribe();
    this.requests = [];
  }

  private repeatRequest(requestWithNewToken: HttpRequest<any>, subscriber: Subscriber<any>) {
    this.http.request(requestWithNewToken).subscribe((res) => {
      subscriber.next(res);
    },
      (err) => {
        if (err.status === 401) {
          this.authService.logout();
        }
        subscriber.error(err);
      },
      () => {
        subscriber.complete();
      });
  }

}
Dmitry
  • 117
  • 1
  • 9
0

I think the problem is with your intercept method as you are wrapping the originalObservable it doesn't get called properly.

Try something like this:

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (!req.url.includes('api/')) {
      return next.handle(req);
    }

   return next
      .handle(req)
      .catch((err) => {
          if (err.status === 401) {
            this.handleUnauthorizedError(req);
          } else {
            return next.handle(req);
          }
        })
      );    
  }
Berk Kurkcuoglu
  • 1,453
  • 1
  • 9
  • 11