Using Angular interceptors to ensure authenticated user state
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:
@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:
- 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 anintercept()
method, which is done on line 6. - The intercept method on line 6 gets passed two parameters:
- the first one being of type
HttpRequest
, which gets the information from a previous interceptor or if there is none, directly from the service - 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.
- the first one being of type
- 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 usingclone()
to and supplying an object with the changes we’d like to see in the request we’re intercepting. - Finally, in lines 14 - 17, we’re performing the action of
- modifying and sending the request through
handle()
(line 14) and - respond to the server response using an Rx.js chain (
pipe()
in line 15).
- modifying and sending the request through
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:
- Reset a timer that triggers the display of a modal that warns the user of an impending logout
- 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 modallogoutUser()
gets called, when the server response to a call is a401 Unauthorized
error or the renewal of the session failed.
So our Rx.js chain looks like this:
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):
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
- calls
resetSessionTimeout()
, which- checks whether the event contains an
HttpResponse
, and if so - reads the
X-eos
header from the server providing the session expiration time and - calls the service to reset the timer that triggers the display of a warning modal to the user.
- (not shown): If
setEosTimer
receives an invalid timestamp, it throws an Exception
- checks whether the event contains an
- if the previous method throws an exception, the Rx chain catches that error to
- log the user out, by explicitly setting the cookie’s expiration time to January 1st, 1970 and
- redirecting the user to the login page using
window.location
, since the login functionality ist part of a separate microservice with its own URL.
- If no exception was thrown by
resetSessionTimeout()
, thecatchError()
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.