Angular: Auto Session Recovery Interceptor

Samarpan Bhattacharya
6 min readApr 7, 2019

--

Most of us already know what an interceptor is. Most of us also know how it is implemented in Angular 7. If not, you can take a look at Angular’s documentation here.

This article is not about understanding interceptors. But it’s a prerequisite for this article. It caters to a very specific and often required use-case in our web applications. So, let’s talk about the use case first.

The Use Case

  1. A user logs into an application with username and password.
  2. REST API responds with an access token and a refresh token.
  3. The access token is needed to be sent in headers for every HTTP request made thereafter. API verifies the validity of token before responding with data for every request.
  4. The access token has an associated expiry duration, say 15 minutes. After 15 minutes, the access token becomes invalid (expired) and API responds with 401 error to any HTTP request made using it.
  5. The UI appplication can then use the refresh token to send another HTTP request which will return a new valid pair of access token and refresh token.
  6. The new access token need to be sent in headers now to retry the last request.

Now the challenges here.

  1. Point #5 and #6 need to happen behind the scene. That means, logged in user should not be shown any error and the action he is performing should succeed. The user experience need to be seamless.
  2. There can be many parallel requests coming in using the expired access token. So, we need to make sure all of them are retried with the new access token.
  3. The refresh token API must be hit only once in case of parallel requests failing.

Inspiration

There are many stack overflow questions on how to do this in Angular. Some are unanswered, some are partially answered and some are well answered. In fact, I also provided an answer to one such question here. So, a question may arise, why this article ? Well, actually none of those explain why exactly the solution works, detailing each executable statement there. Not even mine. Sad.

One SO user asked to provide more explanation on my solution. That kind of inspired me to write down an article explaining each line of code I wrote to solve this problem.

The Solution

First, let’s establish the fact that we need to create an interceptor for this. In order to automate the point #5 and #6, we need to have a way to intercept the responses coming in from http client before it reaches the subscriber. We all know the way to do that is — Interceptor.

So, we are going to create an HTTP interceptor. Here is the interceptor code. I’ll be explaining each statement shortly. First, let’s see the code.

Let’s understand the code now

The services

First, the two services injected in constructor.

  1. UserSessionStoreService - A simple wrapper service around angular-webstorage-service to store access token and refresh token in local storage. You can refer to gist here.
  2. AuthService - A wrapper service for login, refresh token API hits. I have omitted the implementation of this from the article as its out of scope.

The intercept() method

if (req.url.endsWith("/logout") || req.url.endsWith("/token-refresh")) {    return next.handle(req);}

Here, we are checking if the API url is for logout or token-refresh . We don’t want to handle token expiry error for these APIs. token-refresh API is hit from within this interceptor itself, so it will cause infinite loop if we do not ignore it. A token expiry error for logout API is not significant to handle as user is going to be logged out of the application anyways. So, we ignore these from session recovery handling.

return next.handle(req).pipe(catchError((error, caught) => { if (error instanceof HttpErrorResponse) {  if (this._checkTokenExpiryErr(error)) {   return this._ifTokenExpired().pipe(    switchMap(() => {     return next.handle(this.updateHeader(req));    })   );  } else {   return throwError(error);  } } return caught; }));

Now, if the request url does not belong to one of the ignored urls, then, we pipe to the request observable via catchError operator. It will catch any error event coming in response. So, if any error response is returned (other than 2xx response code), this error handler will be invoked.

Inside the error handler, we need to ensure that we handle only HTTP error. Hence, the check.

if (error instanceof HttpErrorResponse) {

Next, we check whether the HTTP error is 401 and it is indeed coming due to access token expired. In my case, whenever any API is hit with expired access token, it throws 401 error code with message ‘TokenExpired’. That is what is checked in the below method.

private _checkTokenExpiryErr(error: HttpErrorResponse): boolean {
return (
error.status &&
error.status === 401 &&
error.error &&
error.error.message === "TokenExpired"
);
}

Once, it passes this check, we are confirmed that the access token has expired.

The recovery logic

We keep an RxJS Subject instantiated as a private member of this service. Why Subject ? Read this from RxJS documentation.

A Subject is like an Observable, but can multicast to many Observers.

Remember, we need to handle parallel requests and ensure only one token-refresh API is hit. Since, Subject can multicast, its best suited for this scenario.

Recovery logic is in this method.

private _ifTokenExpired() { this._refreshSubject.subscribe({  complete: () => {   this._refreshSubject = new Subject<any>();  } }); if (this._refreshSubject.observers.length === 1) {  this.sessionService.refreshToken()
.subscribe(this._refreshSubject);
} return this._refreshSubject;}

First, we add a subscription to the _refreshSubject. This will re-instantiate the _refreshSubject, on process completion, so that we are not having stale observers lying.

this._refreshSubject.subscribe({ complete: () => {  this._refreshSubject = new Subject<any>(); }});

Next, we check whether its the first request or subsequent parallel request. We need to ensure that we invoke token-refresh API for the first request only. The only way to check it is checking the observers length, which will be 1 for first request.

if (this._refreshSubject.observers.length === 1) { this.sessionService.refreshToken()
.subscribe(this._refreshSubject);
}

If it is 1, then we invoke the token-refresh API and add the _refreshSubject as Observer using subscribe. This will make our _refreshSubject’s next method (and subsequently complete method on completion) to be called upon response from token-refresh API. This is another reason to have a Subject here.

Every Subject is an Observer. It is an object with the methods next(v), error(e), and complete(). To feed a new value to the Subject, just call next(theValue), and it will be multicasted to the Observers registered to listen to the Subject.

Finally, we return this _refreshSubject instance.

Retry requests

Now, the token is refreshed but we still need to retry our errored requests. Hence, comes our second part of recovery logic.

We pipe to the above instance using switchMap operator.

switchMap - Projects each source value to an Observable which is merged in the output Observable, emitting values only from the most recently projected Observable.

This operator will switch the _refreshSubject observable with our new observable, which is going to just re-send the same request again creating a new observable.

In case of parallel requests coming in, while the token-refresh API is in progress, the _refreshSubject will take care of it as it will wait for response and then multi-cast to all piped requests.

See here.

switchMap(() => { return next.handle(this.updateHeader(req));})

But before resend, we need to ensure that we send the newly received access token into headers. That is what is done in the updateHeader method.

updateHeader(req) { const authToken = this.store.getAccessToken(); req = req.clone({  headers: req.headers.set("Authorization", `Bearer ${authToken}`) }); return req;}

We clone the old request and update the Authorization header with new access token from store service.

That’s it.

This entire flow will seamlessly work in case of parallel requests as well, since, we are using a Subject, that can multi cast. Very important to note here.

Conclusion

We successfully implemented the use case we were provided with as less code as possible. This session recovery interceptor is mostly useful in case of Oauth strategies. But its not restricted to that. Feel free to tweak it for your specific API use case.

Remember the key to the implementation was Subject and switchMap.

Interceptors are awesome and have very nice use cases that it can cater to. Use them for any and every HTTP request-response transformation. That will keep your code DRY.

Thanks for reading

Thanks for making it till the end. I hope it was helpful to you in understanding the nitty-gritty of the implementation. Please let me know in comment down below if you have any queries or suggestions.

Please give a clap 👏 for my article if you think it was useful.

--

--

Samarpan Bhattacharya

Senior Technical Architect (Web Apps) at SourceFuse Technologies