Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular HttpInterceptor: How to use RxJS for multiple conditions

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?

like image 541
James Avatar asked Oct 17 '18 06:10

James


Video Answer


1 Answers

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 })
}
like image 198
Buggy Avatar answered Sep 28 '22 04:09

Buggy