Within my app I am using the Angular HttpInterceptor to track each outgoing HTTP request and add a JWT token stored in local storage to the request headers so that the backend API can authorize the request by checking the JWT token.
To do this, the interceptor simply takes the HTTP request and adds the token by setting the headers and then calls next.handle(req)
to propagate the request.
This is done using the below code:
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
req = this.addHeaders(req);
return next.handle(req).pipe(
catchError(error => {
if (error instanceof HttpErrorResponse && error.status === 401) {
this._authService.signOut("unauthorized");
}
if (error instanceof HttpErrorResponse && error.status === 403) {
this._authService.signOut("expired");
}
return throwError(error);
})
);
}
/**
* Add access_token to header.
* @param req
*/
addHeaders(req: HttpRequest<any>): HttpRequest<any> {
return req.clone({
setHeaders: {
"Content-type": "application/json",
token: this._authService.getAuthenticationToken(),
jwt_token: this._authService.getAuthorizationToken(),
[this._config.subscriptionKeyName]: this._config.subscriptionKey || ""
}
});
}
I want to extend this functionality so that the interceptor first checks to ensure that the JWT token stored in browser local storage is valid. If the token is not valid, the interceptor itself should fire off a separate HTTP request to retrieve a new JWT token from an API endpoint which should be used to overwrite the old one before continuing with the original request.
I'm trying to figure out how to construct this behaviour in my Angular code, but I keep running into infinite loops.
This is the code I have at the moment.
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (req.headers.has("newtokenrequest")) {
next.handle(req);
}
let jwtToken: string = this._authService.getLocalAuthorizationToken();
if (!this.checkJwtTokenIsValid(jwtToken)) { //Invalid token, get new one
const authTokenStream = this._authService.getApiAuthorizationToken$();
authTokenStream.subscribe(token => {
this._authService.setAuthorizationToken(token);
});
}
req = this.addHeaders(req);
return next.handle(req).pipe(
catchError(error => {
if (error instanceof HttpErrorResponse && error.status === 401) {
this._authService.signOut("unauthorized");
}
if (error instanceof HttpErrorResponse && error.status === 403) {
this._authService.signOut("expired");
}
return throwError(error);
})
);
}
And this is the code that returns the Observable<string>
that retrieves the new JWT token.
getApiAuthorizationToken$(): Observable<string> {
let headers = new HttpHeaders();
headers = headers.set('newtokenrequest', 'true');
return this._httpClient.get<string>(this._configService.getConfig().teacherAuthAPI, {headers: headers});
}
The code gets down to the authTokenStream subscription and then loops back again to the top. I assume this is because the subscription is sending off a new HTTP request to the backend to retrieve the token, however this request should include the newtokenrequest
as a header, which should be detected by the intercept method which should call next.handle(req)
, allowing the request to continue on with no further interaction, however this doesn't happen and instead the code loops instead.
Edit: Update
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (req.headers.has("newtokenrequest")) {
return next.handle(req);
}
let jwtToken: string = this._authService.getLocalAuthorizationToken();
if (!this.checkJwtTokenIsValid(jwtToken) || jwtToken === undefined) { //Invalid token, get new one
const authTokenStream = this._authService.getApiAuthorizationToken$();
authTokenStream.subscribe(token => {
this._authService.setAuthorizationToken(token);
return next.handle(req);
});
}
req = this.addHeaders(req);
return next.handle(req).pipe(
catchError(error => {
if (error instanceof HttpErrorResponse && error.status === 401) {
this._authService.signOut("unauthorized");
}
if (error instanceof HttpErrorResponse && error.status === 403) {
this._authService.signOut("expired");
}
return throwError(error);
})
);
}
getApiAuthorizationToken$(): Observable<string> {
let headers = new HttpHeaders({
newtokenrequest: 'true'
});
// return this._httpClient.get<string>(this._configService.getConfig().teacherAuthAPI, {headers: headers});
const req = new HttpRequest<string>('GET', this._configService.getConfig().teacherAuthAPI, {headers});
let res = this._httpBackend.handle(req).pipe(map((response: HttpResponse<string>) => response.body),);
return res;
}