7

Here is AuthInterceptor:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    constructor(private authService: AuthService) { }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

        const Token = this.authService.getToken();

        if (!Token) {
            return next.handle(req);
        }
 
        // Refresh Token first
        if (Token.expiresRefreshToken && Number(Token.expiresRefreshToken) < Date.now()) {
            this.authService.refreshTokenRefresh(Token.tokenref)
            .subscribe((response) => {
                localStorage.setItem('tokenref', response.tokenref);
                localStorage.setItem('tokenrefexp', response.tokenrefexp);
            });
        }
        // Then next Access Token
        if (Token.expiresToken && Number(Token.expiresToken) < Date.now()) {
            this.authService.refreshToken(Token.tokenref)
            .subscribe((response) => {
                localStorage.setItem('token', response.token);
                localStorage.setItem('tokenexp', response.tokenexp);
            });
        }
        // Original request with updated custom headers
        return next.handle(req.clone({
            headers: req.headers
            .set('Authorization', 'Bearer ' + localStorage.getItem('token'))
            .set('X-Auth-Provider', localStorage.getItem('provider'))
        }));
    }

}

I need to evaluate those conditions before sending the request because some custom headers may change after methods refreshToken and refreshTokenRefresh. Is there a way to evaluate everything inside a RxJS operator? First condition (refreshTokenRefresh), then second (refreshToken) and finally the req.

Update: I'm getting this error: RangeError: Maximum call stack size exceeded. How to fix this?

James
  • 1,189
  • 2
  • 17
  • 32

1 Answers1

4

We want to wait until some requests will be completed (evaluate order does not matter?) than do another request.

const queue = this.handleRefreshToke(this.handleRefreshTokenRefresh([])); - place there all request that should be done before we call next.handle. Use the forkJoin to wait until all request (placed in queue) will be completed than map to another Obervable ( mergeMap ).

PS We could also move handleRefreshTokenRefresh and handleRefreshToke to separated HttpInterceptor.

EDITED To prevent recursive call of interceptors we should skip interceptors for refreshTokens call.

export const InterceptorSkipHeader = 'X-Skip-Interceptor';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    constructor(private authService: AuthService) { }

    handleRefreshTokenRefresh(queue: Observable<void>[]) {
        const Token = this.authService.getToken();
        if (Token.expiresRefreshToken && 
            const req = this.authService.refreshTokenRefresh(Token.tokenref)
              .pipe(tap((response) => {
                localStorage.setItem('tokenref', response.tokenref);
                localStorage.setItem('tokenrefexp', response.tokenrefexp);
            }));
            return [...queue, req];
        }
        return queue;
    }

    handleRefreshToke(queue: Observable<void>[]) {
        const Token = this.authService.getToken();
        if (Token.expiresToken && Number(Token.expiresToken) < Date.now()) {
            const req = this.authService.refreshToken(Token.tokenref)
              .subscribe((response) => {
                localStorage.setItem('token', response.token);
                localStorage.setItem('tokenexp', response.tokenexp);
            });
            return [...queue, req];
        }
        return queue;
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
       if (req.headers.has(InterceptorSkipHeader)) {
            const headers = req.headers.delete(InterceptorSkipHeader);
            return next.handle(req.clone({ headers }));
        }

        const Token = this.authService.getToken();

        if (!Token) {
            return next.handle(req);
        }

        const queue = this.handleRefreshToke(this.handleRefreshTokenRefresh([]));

        return forkJoin(queue).pipe(
            mergeMap(()=>{
                return next.handle(req.clone({
                    headers: req.headers
                        .set('Authorization', 'Bearer ' + localStorage.getItem('token'))
                        .set('X-Auth-Provider', localStorage.getItem('provider')),
                }));
            })
        );
    }

}

Add InterceptorSkipHeader to refreshTokens to skip interceptors.

// AuthService

refreshTokenRefresh(token){
   const headers = new HttpHeaders().set(InterceptorSkipHeader, '');
   return this.httpClient
    .get(someUrl, { headers })
}

refreshToken(token){
   const headers = new HttpHeaders().set(InterceptorSkipHeader, '');
   return this.httpClient
    .get(someUrl, { headers })
}
Buggy
  • 3,539
  • 1
  • 21
  • 38
  • evaluate order does matter because i need a valid refresh token (first condition) to generate an access token (second condition). – James Oct 17 '18 at 13:56
  • 1
    You can use `concat` instead of `forkJoin` – Buggy Oct 17 '18 at 15:13
  • can you validate my answer? i think there may be something wrong because my server is a little unstable: restarts many times when using interceptor. I'm getting this error on console `RangeError: Maximum call stack size exceeded`. – James Oct 17 '18 at 20:46
  • 1
    You need to skip [interceptor](https://stackoverflow.com/q/46469349/5801495) for `handleRefreshToken ` and `handleRefreshTokenRefresh `. – Buggy Oct 18 '18 at 07:07
  • Thank you! I've tried to stop calling interceptor with `HttpBackEnd` before but it didn't worked. – James Oct 18 '18 at 14:01