Understanding Angular Interceptors

with practical examples

Interceptors in Angular, as the name suggests, is a simple way provided by the framework to intercept and modify the application’s http requests globally before they are sent to the server. That really comes in handy, allowing us to configure authentication tokens, add logs of the requests, add custom headers that out application may need and much more.

“Interceptors can perform a variety of implicit tasks, from authentication to logging, in a routine, standard way, for every HTTP request/response. Without interception, developers would have to implement these tasks explicitly for each HttpClient method call.”

Implementing an Interceptor

In order to implement an Interceptor, you need to create a class that implements the intercept method of the HttpInterceptor interface.

So let’s suppose you want to log in the console every single http request made by the application. Below, I’ve created a simple interceptor that would achieve the following.

@Injectable()

export class RequestLogInterceptor implements HttpInterceptor { intercept(

request: HttpRequest<any>, next: HttpHandler

) : Observable<HttpEvent<any>> { console.log(request.url);

return next.handle(request); }

}

The intercept method transforms each request into Observables, which later are going to be resolved by calling next.handle(). So, for our implementation it is quite simple: you take the request, log its url and call next.handle() to send it to the server without making any changes to it.

The next object represents the next interceptor in the interceptors chain, since you can have multiple interceptors in your application. Then, the final next in the chain is actually the HttpClient backend handler, which actually sends the request to the server.

Providing the Interceptor

Since interceptors are dependencies of the HttpClient, you must add them to providers in the same injector (or parent) that provides the HttpClient. For instance, supposing you have your HttpClientModule imported in the AppModule, you must add the interceptors to the providers there as well.



import {

import { RequestLogInterceptor } from '...'; ...import { HTTP_INTERCEPTORS } from '@angular/common/ http ';import { RequestLogInterceptor } from '...'; @NgModule({

...

imports: [

HttpClientModule,

...

],

providers: [

{

provide: HTTP_INTERCEPTORS,

useClass: RequestLogInterceptor,

multi: true

},

...

],

...

}) export class AppModule { }

The multi: true option provided tells Angular that you are providing multiple interceptors and is required if that is the scenario. In our example scenario it wouldn’t be necessary, since we’ve only implemented one Interceptor, so I’ve applied it just to highlight this bit of information.

It is also important to have in mind that Angular applies interceptors in the order that you have provided them in your module’s providers.

Handling Authentication

Ok, so now let’s dig into one of the most common usages of Interceptors, which is handling authentication on the application. This time we’ll be actually changing the http request with the interceptor, in order to add Authorization headers to it.

Supposing that our application is setting the logged user information on our localstorage, we are going to read it in order to check if there is someone logged in. In an real world application, we would have a Service to handle all the authentication flow, but for learning purposes I am going to simplify and get that info directly from the localstorage.

@Injectable()

export class AuthenticationInterceptor implements HttpInterceptor { intercept(

request: HttpRequest<any>, next: HttpHandler

) : Observable<HttpEvent<any>> { const storageUser = localStorage.getItem('LoggedUser'); const loggedUser = jsonInfo ? JSON.parse(jsonInfo) : null; if (loggedUser) {

request = request.clone({

headers: req.headers.set(

'Authorization',

loggedUser.authToken

)

});

} return next.handle(request);

}

}

Since we might have open requests in our application - meaning not all of the requests need authentication -, we are not going to throw by default any kind of error if the user is not logged in the application. However, if it is, we are going to send its authentication token to the server.

On the other hand, we still might receive an authentication error from the server, by sending a wrong token or an expired one. So we could also resolve that on our AuthenticationInterceptor.

What we are going to do is: in case the request was not successfull, check if it was an Authentication Error (401 Unauthorized). If it was, we are going to log the user out of the application and redirect him to the login page, telling him that the access was denied.

Below, we are going to add this treatment to our AnthenticationInterceptor.

@Injectable()

export class AuthenticationInterceptor implements HttpInterceptor { constructor(

private _router: Router

) { } intercept(

request: HttpRequest<any>, next: HttpHandler

) : Observable<HttpEvent<any>> { const storageUser = localStorage.getItem('LoggedUser');

const loggedUser = jsonInfo ? JSON.parse(jsonInfo) : null; if (loggedUser) {

request = request.clone({

headers: req.headers.set(

'Authorization',

loggedUser.authToken

)

});

} return next.handle(request).pipe(

catchError(error => { // Checking if it is an Authentication Error (401)

if (error.status === 401) {

alert('Access Denied');

// <Log the user out of your application code>

this.router.navigate([ 'login-page-route' ]);

return throwError(error);

} // If it is not an authentication error, just throw it

return throwError(error);

})

);

}

}

By calling catchError through a pipe we are able to handle response errors from the server for that request. Therefore, we are allowed to verify its status and do our treatment as mentioned before.

By the way, this is the treatment I have set for learning purposes, but in your real world application here would fit any kind of treatment. Maybe you do not want to redirect the user or maybe you are going to attempt a refresh token api call if an 401 error occurs. Whatever that is necessary for your application.

And lastly, let’s not forget to provide it to our module.



import {

import { AuthenticationInterceptor } from '...'; ...import { HTTP_INTERCEPTORS } from '@angular/common/ http ';import { AuthenticationInterceptor } from '...'; @NgModule({

...

providers: [

{

provide: HTTP_INTERCEPTORS,

useClass: AuthenticationInterceptor

},

...

],

...

}) export class AppModule { }

Handling Impersonation

Now let’s see another type of interception; supposing you are going to have some type of impersonation in your application. By that I mean that somewhere on your application you want to allow an user (like the system admin) to do server requests “pretending to be” another user .

I am going to assume we have an ImpersonationService in our application that will deal with all the impersonation logic, which I won’t be diving into. And from that service we will retrieve information whether that request is supposed to be impersonated or not; and in case it is, the information required for the server, like the impersonated user id.

Then, in our interceptor the idea is simple:

- Check wheter the request should be impersonated;

- If it is an impersonation, send the appropriate header in the request.

@Injectable()

export class ImpersonationInterceptor implements HttpInterceptor { constructor(

private _impersonationService: ImpersonationService

) { } intercept(

request: HttpRequest<any>, next: HttpHandler

) : Observable<HttpEvent<any>> { const impersonatedUser: User | null =

this._impersonationService.getImpersonatedUser(); if (impersonatedUser && impersonatedUser.id) {

request = request.clone({

setHeaders: {

'ImpersonatedUserId': impersonatedUser.id

}

});

}

}

Then, again, let’s not forget to add our new interceptor to our module, joining the previous AuthenticationInterceptor we have created previously.