Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 8 Intercept call to refresh token

I am trying to refresh access token if current access token is expired.

I am sending multiple requests at one time and I want to make a kind of a queue, so other requests won't request refreshing token route.

I've googled some best practises and examples and found out the following solution for Angular 6 and rxjs v6, which is using BehaviourSubject and switchMaps. (please see attached code)

However I am using Angular 8 (8.1) and rxjs v6.4 and this solution does not work for me.

It simply does not reach switchMap in this.authService.requestAccessToken().pipe. (Tested using console.log)

However if I comment return this.refreshTokenSubject.pipe and return next.handle(request) it reaches that switchMap, but my other requests are failed.

Do you know if anything has been changed or should I try doing this in another way?

  • TokenInterceptor
    import { Injectable } from '@angular/core';
    import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
    import { AuthService } from './auth.service';
    import { Observable, BehaviorSubject, Subject } from 'rxjs';
    import { switchMap, take, filter } from 'rxjs/operators';
    @Injectable()
    export class TokenInterceptor implements HttpInterceptor {
        private refreshTokenInProgress = false;
        private refreshTokenSubject: Subject<any> = new BehaviorSubject<any>(null);

        constructor(public authService: AuthService) { }
        intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

            const accessExpired = this.authService.isAccessTokenExpired();
            const refreshExpired = this.authService.isRefreshTokenExpired();

            if (accessExpired && refreshExpired) {
                return next.handle(request);
            }
            if (accessExpired && !refreshExpired) {
                if (!this.refreshTokenInProgress) {
                    this.refreshTokenInProgress = true;
                    this.refreshTokenSubject.next(null);
                    return this.authService.requestAccessToken().pipe(
                        switchMap((authResponse) => {
                            this.authService.saveToken(AuthService.TOKEN_NAME, authResponse.accessToken);
                            this.authService.saveToken(AuthService.REFRESH_TOKEN_NAME, authResponse.refreshToken);
                            this.refreshTokenInProgress = false;
                            this.refreshTokenSubject.next(authResponse.refreshToken);
                            return next.handle(this.injectToken(request));
                        }),
                    );
                } else {
                    return this.refreshTokenSubject.pipe(
                        filter(result => result !== null),
                        take(1),
                        switchMap((res) => {
                            return next.handle(this.injectToken(request))
                        })
                    );
                }
            }

            if (!accessExpired) {
                return next.handle(this.injectToken(request));
            }
        }

        injectToken(request: HttpRequest<any>) {
            const token = this.authService.getToken(AuthService.TOKEN_NAME);
            return request.clone({
                setHeaders: {
                    Authorization: `Bearer ${token}`
                }
            });
        }
    }
  • requestAccessToken
    requestAccessToken(): Observable<any> {
        const refreshToken = this.getToken(AuthService.REFRESH_TOKEN_NAME);
        return this.http.post(`${this.basePath}/auth/refresh`, { refreshToken });
    }

UPD 1

So I used these sources to write my interceptor:

  • https://itnext.io/angular-tutorial-implement-refresh-token-with-httpinterceptor-bfa27b966f57 (Angular 4 solution, I believe it edpends on rxjs version)

  • https://github.com/melcor76/interceptors/blob/master/src/app/interceptors/auth.interceptor.ts

UPD 2

I've excluded refresh request from interceptor scope and now it's working Thanks to @JBNizet

like image 287
AntGrisha Avatar asked Aug 24 '19 12:08

AntGrisha


People also ask

How do I trigger a refresh token?

To use the refresh token, make a POST request to the service's token endpoint with grant_type=refresh_token , and include the refresh token as well as the client credentials if required.

Where does Angular save refresh token?

To implement refresh token, we need to follow 2 steps: save the Refresh Token right after making login request (which returns Access Token and Refresh Token). use Angular HttpInterceptor to check 401 status in the response and call AuthService. refreshToken() with saved Refresh Token above.

When should I call refresh token?

When to use Refresh Tokens? The main purpose of using a refresh token is to considerably shorten the life of an access token. The refresh token can then later be used to authenticate the user as and when required by the application without running into problems such as cookies being blocked, etc.

Are refresh tokens JWTS?

The API returns a short-lived token (JWT), which expires in 15 minutes, and in HTTP cookies, the refresh token expires in 7 days. JWT is currently used for accessing secure ways on API, whereas a refresh token generates another new JWT access token when it expires or even before.


1 Answers

I've excluded refresh request from interceptor scope and now it's working. I've made a temporary fix in order to see it's working in the fastest way.

Now my TokenInterceptor looks like:

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { AuthService } from './auth.service';
import { Observable, BehaviorSubject, Subject } from 'rxjs';
import { switchMap, take, filter } from 'rxjs/operators';
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
    private refreshTokenInProgress = false;
    private refreshTokenSubject: Subject<any> = new BehaviorSubject<any>(null);

    constructor(public authService: AuthService) { }
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (request.url.indexOf('refresh') !== -1) {
            return next.handle(request);
        }

        const accessExpired = this.authService.isAccessTokenExpired();
        const refreshExpired = this.authService.isRefreshTokenExpired();

        if (accessExpired && refreshExpired) {
            return next.handle(request);
        }
        if (accessExpired && !refreshExpired) {
            if (!this.refreshTokenInProgress) {
                this.refreshTokenInProgress = true;
                this.refreshTokenSubject.next(null);
                return this.authService.requestAccessToken().pipe(
                    switchMap((authResponse) => {
                        this.authService.saveToken(AuthService.TOKEN_NAME, authResponse.accessToken);
                        this.authService.saveToken(AuthService.REFRESH_TOKEN_NAME, authResponse.refreshToken);
                        this.refreshTokenInProgress = false;
                        this.refreshTokenSubject.next(authResponse.refreshToken);
                        return next.handle(this.injectToken(request));
                    }),
                );
            } else {
                return this.refreshTokenSubject.pipe(
                    filter(result => result !== null),
                    take(1),
                    switchMap((res) => {
                        return next.handle(this.injectToken(request))
                    })
                );
            }
        }

        if (!accessExpired) {
            return next.handle(this.injectToken(request));
        }
    }

    injectToken(request: HttpRequest<any>) {
        const token = this.authService.getToken(AuthService.TOKEN_NAME);
        return request.clone({
            setHeaders: {
                Authorization: `Bearer ${token}`
            }
        });
    }
}

Thanks to @JBNizet

like image 106
AntGrisha Avatar answered Sep 21 '22 04:09

AntGrisha