Angular 4.3 - HTTP Interceptor - refresh JWT token
My final solution to this problem:
@Injectable()
export class WebApiInterceptor implements HttpInterceptor {
constructor(private tokenService: TokenService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log('*An intercepted httpRequest*', req, this.tokenService.accessToken);
const authReq = this.authenticateRequest(req);
console.log('*Updated httpRequest*', authReq);
return next.handle(authReq)
.map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
console.log('*An intercepted httpResponse*', event);
return event;
}
})
.catch((error: any) => {
if (error instanceof HttpErrorResponse) {
if (error.status === 403 && error.url !== environment.authEndpoint) {
return this.tokenService
.obtainAccessToken()
.flatMap((token) => {
const authReqRepeat = this.authenticateRequest(req);
console.log('*Repeating httpRequest*', authReqRepeat);
return next.handle(authReqRepeat);
});
}
} else {
return Observable.throw(error);
}
})
}
}
Function
authenticateRequest(req)
just adds Authorization header to the copy of original request
Function
obtainAccessToken()
get fresh token form authorization server and stores it
You need to add the catch
operator from RxJS. This is where an error will be, and you can handle it accordingly.
When you get an error of status 0, that most likely means the remote server is down and the connection could not be made.
Take a look at my example logic:
this.http.request(url, options)
.map((res: Response) => res.json())
.catch((error: any) => {
const err = error.json();
// Refresh JWT
if (err.status === 403) {
// Add your token refresh logic here.
}
return Observable.throw(err);
});
In order to get your refresh logic to go through the interceptor, you need to return the invocation, and the function should also return an Observable
. For example modifying the original logic above:
this.http.request(url, options)
.map((res: Response) => res.json())
.catch((error: any) => {
const err = error.json();
// Refresh JWT
if (err.status === 403) {
// refreshToken makes another HTTP call and returns an Observable.
return this.refreshToken(...);
}
return Observable.throw(err);
});
If you want to be able to retry the original request, what you can do is pass the original request data so that on successfully refreshing the token you can make the call again. For example, this is what your refreshToken
function could look like:
refreshToken(url: stirng, options: RequestOptionsArgs, body: any, tokenData: any): Observable<any>
return this.post(`${this.url}/token/refresh`, tokenData)
.flatMap((res: any) => {
// This is where I retry the original request
return this.request(url, options, body);
});
}
Just wanted to share what worked for me:
@Injectable()
export class AutoReLoginInterceptor implements HttpInterceptor {
constructor() {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// as we want to intercept the possible errors, instead of directly returning the request execution, we return an Observable to control EVERYTHING
return new Observable<HttpEvent<any>>(subscriber => {
// first try for the request
next.handle(req)
.subscribe((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// the request went well and we have valid response
// give response to user and complete the subscription
subscriber.next(event);
subscriber.complete();
}
},
error => {
if (error instanceof HttpErrorResponse && error.status === 401) {
console.log('401 error, trying to re-login');
// try to re-log the user
this.reLogin().subscribe(authToken => {
// re-login successful -> create new headers with the new auth token
let newRequest = req.clone({
headers: req.headers.set('Authorization', authToken)
});
// retry the request with the new token
next.handle(newRequest)
.subscribe(newEvent => {
if (newEvent instanceof HttpResponse) {
// the second try went well and we have valid response
// give response to user and complete the subscription
subscriber.next(newEvent);
subscriber.complete();
}
}, error => {
// second try went wrong -> throw error to subscriber
subscriber.error(error);
});
});
} else {
// the error was not related to auth token -> throw error to subscriber
subscriber.error(error);
}
});
});
}
/**
* Try to re-login the user.
*/
private reLogin(): Observable<string> {
// obtain new authorization token and return it
}
}