I have a project, for which I use Tokenauthentication with JWT Tokens. I am relatively new to Angular Development and rxjs in particular, so there are a lot of concepts I am likely not yet familiar with or can't apply properly. My Backend is Django 3, using the Django Rest Framework and rest_framework_simplejwt.
What the interceptor is doing is check any outgoing HTTP request on if it's a call to my API. If it is, attach the JWT Token. If it is and the Access Token is expired, refresh the Access Token first, then send the call to the API. I haven't yet coded in the scenario on what to do if the Refresh Token expires/is close to expiring but I'm doing this step by step and that's next on the list.
I don't like my code here. It's hard for me to grasp to the point I need comments to make it easier. An even bigger problem is that I don't fully understand the code and thus am struggling to split the intercept function into smaller chunks to move into their own functions.
What could I be doing to make it more comprehensible? What could I "split off" into some nicely named function?
Here the code:
//jwt-interceptor.ts
@Injectable()
export class JWTInterceptor implements HttpInterceptor{
private tokenRefreshInProgress: boolean = false;
private refreshAccessTokenSubject: Subject<any> = new BehaviorSubject<any>(null);
constructor(private userService: UserService){}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{
if (this.isApiUrl(request.url)){
const accessToken = this.userService.getAccessToken();
//Below: If Access Token Expired and no refresh of it currently running
if(this.userService.isTokenExpired(accessToken) && !this.tokenRefreshInProgress){
this.tokenRefreshInProgress = true;
this.refreshAccessTokenSubject.next(null);
return this.userService.refreshToken().pipe(
switchMap(authResponse => {
this.userService.setAccessToken(authResponse.access);
this.tokenRefreshInProgress = false;
this.refreshAccessTokenSubject.next(authResponse.access);
request = this.addTokenToRequest(authResponse.access, request);
return next.handle(request);
})
)
//Below: If Access Token is expired and a refresh of it already running
} else if(this.userService.isTokenExpired(accessToken) && this.tokenRefreshInProgress){
return this.refreshAccessTokenSubject.pipe(
filter(result => result !== null),
first(),
switchMap(response => {
request = this.addTokenToRequest(this.userService.getAccessToken(), request);
return next.handle(request);
})
)
//Below: If Access Token Valid
} else {
request = this.addTokenToRequest(accessToken, request);
}
}
return next.handle(request);
}
isApiUrl(url: string): boolean{
const isApiUrl: boolean = url.startsWith(Constants.wikiApiUrl);
const isTokenLoginUrl: boolean = url.endsWith('/token');
const isTokenRefreshUrl: boolean = url.endsWith('/token/refresh');
return isApiUrl && !isTokenLoginUrl && !isTokenRefreshUrl;
}
addTokenToRequest(token: string, request: HttpRequest<any>): HttpRequest<any>{
const httpHeaders = new HttpHeaders().set("Authorization", `Bearer ${token}`);
request = request.clone({headers: httpHeaders});
return request;
}
}
//methods from UserService class in user.service.ts
isTokenExpired(token: string): boolean{
const [encodedHeader, encodedPayload, encodedSignature] = token.split('.');
const payload = JSON.parse(atob(encodedPayload));
const expiryTimestamp = payload.exp;
const currentTimestamp = Math.floor((new Date).getTime()/1000);
return currentTimestamp >= expiryTimestamp;
}
getRefreshToken(): string{
return localStorage.getItem('refresh_token');
}
getAccessToken(): string{
return localStorage.getItem('access_token');
}
setAccessToken(accessToken: string): void{
localStorage.setItem('access_token', accessToken);
}
refreshToken(): Observable<{access: string, refresh: string}>{
const refreshToken = this.getRefreshToken();
return this.http.post<{access, refresh}>(Constants.wikiTokenRefreshUrl, {refresh: refreshToken});
}
```