angularangular2-jwt

Angular - JWT Refresh Token


After a long time I didn't find an approach about refresing tokens TTL: 30 minutes Refresh TTL: 2 Weeks

If I refresh the page after 45 minutes innactive then I make a getAccessToken() function to send the expired token and then send me back a refreshed token. The big problem is that if my page makes more than 1 ajax request then if the first request invalidate my token the second request force my to relogin becauce it send empty token

@NgModule({
providers: [
    {
        provide: AuthHttp,
        useFactory: authHttpServiceFactory,
        deps: [Http, RequestOptions, Router]
    }
]
})

export function authHttpServiceFactory(http: Http, options: RequestOptions, router: Router) {
return new AuthHttp(new AuthConfig({
    tokenName: 'token',
    tokenGetter: (() => getAccessToken(http,router)),
    //tokenGetter: (() => localStorage.getItem('JWToken')),
    globalHeaders: [{'Content-Type': 'application/json'}],
    noJwtError: true,
}), http, options);
}


function getAccessToken(http: Http, router:Router): Promise<string> {

let jwtHelper: JwtHelper = new JwtHelper();

let accessToken = localStorage.getItem('JWToken');

if( accessToken == '' || !accessToken || accessToken == undefined || accessToken == null){
    router.navigate(['./admin/login']);
    return;
}


if (jwtHelper.isTokenExpired(accessToken)) {
    return new Promise((resolve, reject) => {
        let refreshTokenService: RefreshTokenService = new RefreshTokenService(http);
        refreshTokenService.refreshToken(accessToken).subscribe((res: any) => {
            res = res.json();

            if(res.token) {
                localStorage.setItem('JWToken', res.token);
                resolve(res.token);

            }else{
                localStorage.removeItem('JWToken');

                router.navigate(['./admin/login']);
            }
        });
    });
} else {
    return Promise.resolve(accessToken);
}
}

I want the requests wait for the response of the first request


Solution

  • And this is the injector for the new httpClient library

    import {Injectable, Injector} from "@angular/core";
    import {HttpEvent, HttpHandler, HttpInterceptor, HttpResponse} from "@angular/common/http";
    import {HttpRequest} from "@angular/common/http";
    import {Observable} from "rxjs/Observable";
    import {SiteService} from "../services/site.service";
    import {Router} from "@angular/router";
    import {LoadingService} from "../../components/loading/loading.service";
    import {AuthenticationService} from "../services/authentication.service";
    
    @Injectable()
    export class AuthInterceptor implements HttpInterceptor {
    
    constructor(private router: Router,
                private siteService: SiteService,
                private loadingService: LoadingService,
                private injector: Injector) {
    }
    
    
    
    private fixUrl(url: string) {
        if (url.indexOf('http://') >= 0 || url.indexOf('https://') >= 0)
            return url;
        else
            return this.siteService.apiDomain() + url;
    }
    
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    
        let clonedRequest;
        if ( this.siteService.getJWToken() !== null ) {
            clonedRequest = req.clone({
                headers: req.headers.set('Authorization', 'Bearer ' + this.siteService.getJWToken()),
                url: this.fixUrl(req.url)
            });
        } else {
            clonedRequest = req.clone({
                url: this.fixUrl(req.url)
            });
        }
    
        let authenticationService = this.injector.get(AuthenticationService);
    
        this.loadingService.start();
        const started = Date.now();
        return next.handle(clonedRequest)
            .do(event => {
                if (event instanceof HttpResponse) {
    
                    const elapsed = Date.now() - started;
                    console.log('%c Request for ' + this.fixUrl(req.urlWithParams) + ' took ' + elapsed + ' ms.', 'background: #222; color: yellow');
                }
            })
            ._finally(() => {
                this.loadingService.stop();
            })
            .catch((res) => {
                if ((res.status === 401 || res.status === 403) && res.error.error === 'token_expired') {
                    this.loadingService.start();
                    return authenticationService.refreshToken().flatMap((data: any) => {
                        this.loadingService.stop();
                        if (data.token !== '') {
                            this.siteService.setCurrentUser(data.user);
                            this.siteService.setCurrentUserPermissions(data.permissions);
                            this.siteService.setJWToken(data.token);
                        } else {
                            this.siteService.removeCurrentUser();
                            this.siteService.removeCurrentUserPermissions();
                            this.siteService.removeJWToken();
                            this.router.navigate(['./auth/login']);
                            return Observable.throw(res);
                        }
                        let clonedRequestRepeat = req.clone({
                            headers: req.headers.set('Authorization', 'Bearer ' + this.siteService.getJWToken()),
                            url: this.fixUrl(req.url)
                        });
                        return next.handle(clonedRequestRepeat).do(event => {
                            if (event instanceof HttpResponse) {
    
                                const elapsed = Date.now() - started;
                                console.log('%c Request for ' + req.urlWithParams + ' took ' + elapsed + ' ms.', 'background: #222; color: yellow');
                            }
                        });
                    })
                } else if (res.status === 400 && res.error.error === 'token_not_provided') {
                    this.router.navigate(['./auth/login']);
                    return Observable.throw(res);
                } else if (res.status === 401 && res.error.error === 'token_invalid') {
                    this.router.navigate(['./auth/login']);
                    return Observable.throw(res);
                } else {
                    return Observable.throw(res);
                }
    
            });
    
    }
    }
    

    And do not forget to send cache (browser) headers to the backend response for at least some seconds.