Torsten Müller

Using Angular interceptors to ensure authenticated user state

published Apr 4th, 2020

Ok, here we go: Geek alert. I admit that when I first heard about Angular interceptors, I had to think of “Pirates of the Caribbean” and there “… not being a real ship as can match The Interceptor for speed.” (Hence the image above)

But in the Angular context, HTTP Interceptors actually can abstract out and provide a place for some fundamental functionality that should be available throughout the application. While I’m going to discuss managing the authentication status in detail in this post, there are many other uses around such as performance logging of api calls, logging or resolving of API errors etc. You can find a list of 10 use cases for interceptors on medium.com.

The problem to solve

I was working on an Angular app, where the login status was managed server-side, of course. The problem was that the expiration time was stored in a DB on the server and all the Auth cookies were set as session cookies and as HTTP Only and secure. So no browser access, and even if I had browser access, there was no expiration timestamp to be read.

With that setup, the server was in lone control of the login status of the user, not even permitting the frontend to provide warning to a user that the session was about to expire.

Unacceptable!

From a user experience angle, it is simply good practice to alert a user that he or she is about to be logged out and provide a means to extend a session. With all the state managed server-side, this was solved in a previous project through continuous pings every X seconds to the backend to see if an authentication error was thrown and then logging the user out — without warning!

So if the session expired server-side, the frontend would find out in a timely manner, but too late: The user would be logged out, regardless of whether he or she had already worked on a form for 10 minutes and would lose all that work. On top of that, the pinging solution created a lot of unnecessary network traffic and processing overhead with constantly setting up and tearing down HTTP connections to the server.

I desperately wanted to do better; Hence the implementation of a client-side interceptor with a modification to the server-side code. To get around the problem of not being able to read the cookies, the backend sets a custom header on every HTTP response, which is readable by JavaScript. That header contains the current expiration timestamp of the session. On the Java server-side, interestingly, this was also solved with the concept of interceptors.

How an Angular Interceptor works

An Angular interceptor gets called by (“intercepts”) any AJAX call to the backend. This happens both on the way to the server as well as on the way back from the server to the browser —– Thus it can modify the request on the way to and react to an answer from the server before the Angular app gets to process it as an HttpResponse. The basic setup of an interceptor is as follows:

auth.interceptor.ts
@Injectable({ providedIn: 'root '})
export class AuthInterceptor implement HttpInterceptor {

  constructor() {}

  public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    req = req.clone({
      setHeaders: {
        X-my-header: 'some-token-value'
      }
    });

    return next.handle(req)
      .pipe(
        ...
      );
  }
}

That’s a lot of code in only 20 lines, so let’s take this step by step:

  1. In lines 1 and 2, we set up the interceptor. It’s essentially, in Angular parlance, a service that implements the HttpInterceptor interface. That interface requires the implementation of an intercept() method, which is done on line 6.
  2. The intercept method on line 6 gets passed two parameters:
    1. the first one being of type HttpRequest, which gets the information from a previous interceptor or if there is none, directly from the service
    2. the second one being an HttpHandler, which we’re going to call to pass the results of this interceptor to the next one in the chain, or to the server, if we’re the last — or only — one.
  3. Lines 8 through 12 show how to modify the outgoing HTTP request. To note here is that the request, req, is an immutable object. So we’re creating a new instance by using clone() to and supplying an object with the changes we’d like to see in the request we’re intercepting.
  4. Finally, in lines 14 - 17, we’re performing the action of
    1. modifying and sending the request through handle() (line 14) and
    2. respond to the server response using an Rx.js chain (pipe() in line 15).

Those are the basics for an interceptor, so now we can go and implement the updating

The AuthInterceptor

First off, in my case, I did not need to modify the request going out. An example, where that modification would be necessary is the use of Bearer tokens for authentication, such as in OAuth. If the token value was stored in localStorage, we would need to modify each server request with a corresponding header passing the value of the token.

Since that’s not the case here, the implementation only uses the response part to achieve the following goals:

  1. Reset a timer that triggers the display of a modal that warns the user of an impending logout
  2. Log the user out, if the response from the server indicates that the session has expired (or never was started)

Without going into too many details, this implementation uses an AuthService to manage the handling of the login status. That service has two methods:

  • resetSessionTimeout() reads the header of the server response and restarts the timer for the warning modal
  • logoutUser() gets called, when the server response to a call is a 401 Unauthorized error or the renewal of the session failed.

So our Rx.js chain looks like this:

auth.interceptor.ts
return next.handle(req)
  .pipe(
    tap(this.resetSessionTimeout(this.authService)),
    catchError(this.logoutUser(this.authService)),
    finalize( () => {} )
  )

with an implementation for resetSessionTimeout() (going to omit the implementation for logoutUser() method):

auth.interceptor.ts
public resetSessionTimeout(authService: AuthService) {
  return (response: HttpResponse<any>): void => {
    if (response instanceof HttpResponse) {
      const eosHeader = response.headers.get('x-eos');
      authService.setEosTimer(new EosTime(eosHeader));
    }
  }
}

Both methods are curried methods accepting the Authenticationservice as their first parameters. This was done to simplify testing the methods. With that, the functionality is straightforward:

Whenever an HttpEvent is received, the interceptor

  1. calls resetSessionTimeout(), which
    1. checks whether the event contains an HttpResponse, and if so
    2. reads the X-eos header from the server providing the session expiration time and
    3. calls the service to reset the timer that triggers the display of a warning modal to the user.
    4. (not shown): If setEosTimer receives an invalid timestamp, it throws an Exception
  2. if the previous method throws an exception, the Rx chain catches that error to
    1. log the user out, by explicitly setting the cookie’s expiration time to January 1st, 1970 and
    2. redirecting the user to the login page using window.location, since the login functionality ist part of a separate microservice with its own URL.
  3. If no exception was thrown by resetSessionTimeout(), the catchError() method is not called and the user stays logged in — with an updated timer.

Summary

In this post I have introduced, and hopefully sufficiently explained, the concept of interceptors in Angular and coded one use case: The management of user login status based on the headers set by the server on HTTP Requests.

The great benefit of this solution, over for example handling the login status in the services talking to the backend lies in the fact that the interceptor runs every time without duplicating the code, as would be the case if we implemented that same functionality in each service.