Skip to content

Instantly share code, notes, and snippets.

@abereghici
Last active December 29, 2023 17:54
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save abereghici/054cefbdcd8ccd3ff03dcc4e5155242b to your computer and use it in GitHub Desktop.
Save abereghici/054cefbdcd8ccd3ff03dcc4e5155242b to your computer and use it in GitHub Desktop.
Angular Tutorial — Implement Refresh Token with HttpInterceptor
// src/app/services/token-interceptor.service.ts
import { Injectable } from "@angular/core";
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from "@angular/common/http";
import { AuthenticationService } from "../authentication.service";
import { Observable } from "rxjs/Observable";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
private refreshTokenInProgress = false;
// Refresh Token Subject tracks the current token, or is null if no token is currently
// available (e.g. refresh pending).
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(
null
);
constructor(public auth: AuthenticationService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).catch(error => {
// We don't want to refresh token for some requests like login or refresh token itself
// So we verify url and we throw an error if it's the case
if (
request.url.includes("refreshtoken") ||
request.url.includes("login")
) {
// We do another check to see if refresh token failed
// In this case we want to logout user and to redirect it to login page
if (request.url.includes("refreshtoken")) {
this.auth.logout();
}
return Observable.throw(error);
}
// If error status is different than 401 we want to skip refresh token
// So we check that and throw the error if it's the case
if (error.status !== 401) {
return Observable.throw(error);
}
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
return this.refreshTokenSubject
.filter(result => result !== null)
.take(1)
.switchMap(() => next.handle(this.addAuthenticationToken(request)));
} else {
this.refreshTokenInProgress = true;
// Set the refreshTokenSubject to null so that subsequent API calls will wait until the new token has been retrieved
this.refreshTokenSubject.next(null);
// Call auth.refreshAccessToken(this is an Observable that will be returned)
return this.auth
.refreshAccessToken()
.switchMap((token: any) => {
//When the call to refreshToken completes we reset the refreshTokenInProgress to false
// for the next time the token needs to be refreshed
this.refreshTokenInProgress = false;
this.refreshTokenSubject.next(token);
return next.handle(this.addAuthenticationToken(request));
})
.catch((err: any) => {
this.refreshTokenInProgress = false;
this.auth.logout();
return Observable.throw(error);
});
}
});
}
addAuthenticationToken(request) {
// Get access token from Local Storage
const accessToken = this.auth.getAccessToken();
// If access token is null this means that user is not logged in
// And we return the original request
if (!accessToken) {
return request;
}
// We clone the request, because the original request is immutable
return request.clone({
setHeaders: {
Authorization: this.auth.getAccessToken()
}
});
}
}
@adamturski
Copy link

@abereghici
return next.handle(request).catch(error => {
Above will always throw 401 for secured endpoint, you should add authentication token to request.

@IKafle
Copy link

IKafle commented Jun 6, 2019

this.auth.refreshAccessToken() method is invoked from httpClient service which returns cold observable. So the problem is until subscribe() method is invoked on this.auth.refreshAccessToken() , it wont trigger the GET/POST request to a server. Simply calling switchMap() didn't work in my case. And with subscribe, i have to unsubscribe it on some point to avoid memory leaks.

Any solution to this ???

@igblip
Copy link

igblip commented Aug 14, 2019

Line 74:
return Observable.throw(err);

@desmondchou
Copy link

@cholshwor I think I have your problem before which the refreshAccesssToken() didn't fire HTTP request.
Since I am using Angular 7 with RxJs 6.4, the syntax for the Observable operator is a bit different from above. When I convert the code, I haven't caught the 401 properly. It appears that the refreshAccessToken() didn't fire HTTP request, but in fact, the error returned to the original request subscriber and stopped further processing.

In short, it maybe worth checking whether the 401 is caught.

In RxJs 6.4, my code looks like below:

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // pre request logic return next.handle(requestWithToken(token)).pipe( tap( (event) => { // post request logic }), catchError( ( error ) => { if (error.status == 401) { return refreshTokenRequest().pipe(switchMap((newToken) => next.handle(requestWithToken(newToken))) } return Observable.throw(error); }) }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment