Search code examples
angularangular5angular-http-interceptors

Angular 5 Http Interceptor refreshing JWT token


I already have implemented logic of token saving, retrieving and I have refreshing call also. The problem is that when I intercept 403 in my HttpInterceptor, other calls that are made at the same time, also refresh the token. I would love to hold those calls until my token is refreshed. To create what I would call a 'semaphore' of requests.

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

private auth: AuthService;

constructor(private injector: Injector) {
}

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    this.auth = this.injector.get(AuthService);

    if(this.auth.isAuthenticated()){
        request = request.clone({
            setHeaders: {
                Accept: 'application/json',
                Authorization: `Bearer ${localStorage.getItem('access_token')}`
            }
        });
    } else {
        request = request.clone({
            setHeaders: {
                Accept: 'application/json'
            }
        });
    }

    return next.handle(request).catch(error => {
        if (error.status === 401) {
            console.log('refreshing token');

            // TODO: return Refresh Token here and hold other calls
        }

        return Observable.throw(error);
    });
}

Solution

  • Well, I'm not able to setup an enviroment to test if this logic works properly, but I tried my best:

    Your interceptor should be something like:

    @Injectable()
    export class TokenInterceptor implements HttpInterceptor {
    
      private auth: AuthService;
    
      constructor(private injector: Injector) {
        this.auth = this.injector.get(AuthService);
      }
    
      setHeaders(request) {
        return function (token) {
          return request.clone({
            setHeaders: {
              Accept: 'application/json',
              Authorization: `Bearer ${token}`
            }
          });
        }
      }
    
      intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return this.auth
          .getToken()
          .map(this.setHeaders(request))
          .mergeMap(next.handle)
          .catch(error => {
            if (error.status === 401) {
              return this.auth.refreshToken()
                .map(this.setHeaders(request))
                .mergeMap(next.handle);
            }
            return Observable.throw(error);
          });
      }
    
    }
    

    And your AuthService:

    @Injectable()
    export class AuthService {
    
      private refreshTokenCall;
    
      saveTokenInLocalStorage(token) {
        localStorage.setItem('access_token', token);
      }
    
      getToken() {
        if (localStorage.getItem('access_token')) {
          return Observable.of(localStorage.getItem('access_token'));
        }
    
        return this.refreshToken();
      }
    
      refreshToken() {
        if (!this.refreshTokenCall) {
          this.refreshTokenCall = this.http.get(refreshTokenURL)
            // Maybe a .map() here, it depends how the backend returns the token
            .do(this.saveTokenInLocalStorage)
            .finally(() => this.refreshTokenCall = null);
        }
        return this.refreshTokenCall;
      }
    
    }
    

    I hope it helps you somehow.