JWT refresh token in Angular 6 Interceptor with Ngrx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Injectable } from '@angular/core'; | |
import { | |
HttpRequest, | |
HttpHandler, | |
HttpEvent, | |
HttpInterceptor | |
} from '@angular/common/http'; | |
import { Observable, BehaviorSubject } from 'rxjs'; | |
import { filter, take, switchMap } from 'rxjs/operators'; | |
import { SigninModuleState } from '../../signin/store/models/signin.model'; | |
import { Store } from '@ngrx/store'; | |
import { SIGNIN_STORE } from '../../signin/store/reducer/signin.reducer'; | |
import * as SigninActions from '../../signin/store/actions/signin.actions'; | |
import { JWTUtil } from '../../shared/utils/jwt-util'; | |
@Injectable() | |
export class HttpJwtInterceptor implements HttpInterceptor { | |
private jwtToken: any = { | |
accessToken: "", | |
refreshToken: "" | |
} | |
private refreshTokenInProgress = false; | |
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>( | |
null | |
); | |
constructor(private store: Store<SigninModuleState>, private jwtUtil: JWTUtil) { | |
this.store | |
.select(SIGNIN_STORE) | |
.subscribe((data: SigninModuleState) => { | |
console.log(data) | |
if (this.jwtToken.accessToken == "" || this.jwtToken.accessToken != this.getAccessToken(data)) { | |
this.jwtToken.accessToken = this.getAccessToken(data); | |
this.jwtToken.refreshToken = this.getRefreshToken(data); | |
this.refreshTokenSubject.next(this.jwtToken.refreshToken); | |
} | |
}) | |
} | |
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | |
console.log("PRE INTERCEPT: ", request.url); | |
if (request.url.includes("api/signin") || request.url.includes("api/token/refresh")) { | |
console.log("Login or refresh API!") | |
console.log("POST INTERCEPT: ", request.url); | |
return next.handle(request); | |
} | |
this.refreshTokenInProgress = this.isRefreshTokenInProgress(this.jwtToken.accessToken); | |
if (this.refreshTokenInProgress) { | |
// If refreshTokenInProgress is true, we will wait until refreshTokenSubject has a non-null value | |
// – which means the new token is ready and we can retry the request again | |
console.log("Return Refresh Token In Progress!") | |
return this.refreshTokenSubject.pipe( | |
filter(result => result !== null), | |
take(1), | |
switchMap(() => next.handle(this.addAuthenticationToken(request))) | |
); | |
} else { | |
console.log("In else block. Setting refreshTokenSubject to null!") | |
// Set the refreshTokenSubject to null so that subsequent API calls will wait until the new token has been retrieved | |
this.refreshTokenSubject.next(null); | |
return next.handle(this.addAuthenticationToken(request)); | |
} | |
} | |
private getAccessToken(data: SigninModuleState): string { | |
if (data && data.loginContext) { | |
return data.loginContext.accessToken; | |
} else { | |
return "NOT_AVAILABLE"; | |
} | |
} | |
private getRefreshToken(data: SigninModuleState): string { | |
if (data && data.loginContext) { | |
return data.loginContext.refreshToken; | |
} else { | |
return "NOT_AVAILABLE"; | |
} | |
} | |
addAuthenticationToken(request: HttpRequest<any>) { | |
// Get access token from Local Storage | |
const accessToken = this.jwtToken.accessToken; | |
// If access token is null this means that user is not logged in | |
// And we return the original request | |
if (!accessToken) { | |
console.log("Token not found!") | |
return request; | |
} | |
// We clone the request, because the original request is immutable | |
console.log("Returning cloned request with bearer token!") | |
let newRequest = request.clone({ | |
setHeaders: { | |
Authorization: "Bearer " + accessToken | |
} | |
}); | |
console.log("POST INTERCEPT: ", newRequest.url); | |
return newRequest; | |
} | |
isRefreshTokenInProgress(accessToken: string) { | |
if (this.jwtUtil.isExpired(accessToken)) { | |
console.log("Token expire. Dispatching new token action!") | |
// Set the refreshTokenSubject to null so that subsequent API calls will wait until the new token has been retrieved | |
this.refreshTokenSubject.next(null); | |
this.store.dispatch(new SigninActions.RefreshToken(this.jwtToken)); | |
return true; | |
} | |
else { | |
return false; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment