Angular HTTP Interceptor
Tags: angular
Published: 2020-06-22 20:53:23

Why would you want to setup an HTTP Interceptor? Not sure, but I can tell you that I used it to solve a few different problems, out of which I'll discuss in this post:
- Adding an authentication header
- Handling `401` Unauthorized

*Bonus*: Unit Tests the interceptor (In the next post).

But first, what's an [interceptor](https://angular.io/api/common/http/HttpInterceptor)?
> ... transform the outgoing request before passing it to the next interceptor in the chain ... An interceptor may transform the response event stream as well, by applying additional RxJS operators on the stream.

Or, in human speak, If you need to make changes or decisions to any request or response - this is where you want to do it.

# Add an interceptor to your project
In your `app.module.ts` (or what ever you called the root of your project) you should have this code:

```typescript
import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { TokenInterceptor } from './auth/token.interceptor';
import { AppComponent } from './app.component';

@NgModule({
    declarations: [AppComponent],
    imports: [ ... ],
    providers: [
        {
            provide: HTTP_INTERCEPTORS,
            useClass: TokenInterceptor,
            multi: true
        }
    ],
    bootstrap: [AppComponent]
})
export class AppModule {}
```

What's going on here? We're "providing" the app with a tool. 
- The `HTTP_INTERCEPTORS` is a symbol - a key - to lock our interceptor into position. It lets the system make sure where we want to `provide` something. 
- The `TokenInterceptor` is our class we're about to implement. 
- Finally, `multi: true` means we can provide multiple interceptors, chained, as opposed to overriding each other. In this specific app we only have one interceptor, but if we ever want to add another one, we're ready to go.

# The basic interceptor
```typescript
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
// Note: these are only the initial imports. Add more as needed.

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request);
  }
}
```

We're not really doing anything here, but just to set the basics. Implementing `HttpInterceptor` means we need to implement the `intercept` function which gets a `request` and a handler for continuing the process. Later we'll see what we can do with it.


# Add an authentication header
```typescript
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  request = request.clone({
    setHeaders: {
      'my-auth-token': `${this.getSessionToken()}`
    }
  });
  return next.handle(request);
}
```
You can't simply change the request. The request is a `readonly` object. But you can `clone` it, while overriding specific components. In our scenario, we're using the `setHeaders` property to add a token header to the request.

The function `getSessionToken` is not provided here and is up to you, as a developer to know how it is stored and how to retrieve it.

# Handling backend `401 Unauthorized`
Every page of our application makes several XHR calls to the backend. At some point or other, for various reasons, the user's session may expire. Instead of showing the user a pile of error messages - at first sign of trouble (`401`) we redirect the user to the login page.

```typescript
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  request = request.clone({
    setHeaders: {
      'my-auth-token': `${this.getSessionToken()}`
    }
  });
  return next.handle(request).pipe(
    catchError((response: HttpErrorResponse) => {
      if (response.status === 401) {
        // Do something here
      }
      return throwError(response);
    }
  );
}
```
We've added a pipe after handling the request. This is our way of handling the response. `catchError` is an `rxjs` function which catches if an error is thrown by the observable. We check the response and "Do something" and then throw the response again. We throw the response so the caller of the request, further down the line, knows that something went wrong and can handle it gracefully, regardless of the interceptor's processing.

Now, why "Do something"? The answer is in a hint I gave earlier - each page makes several calls, and all or some may be throwing `401` errors, and we don't want all of them to "hit" the user at the same time. Enter `throttleTime`.

`throttleTime` is a sibling of `debounce`. While `debounce` waits for an action to stop happening, `throttleTime` lets the first action go through and then blocks for a given time. Let's setup a subject to "Do something" while "protected" by our throttle.

```typescript
private throttleLogout = new Subject();
constructor() {
  this.throttleLogout.pipe(throttleTime(5000)).subscribe(url => {
    this.logout();
  });
}

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  request = request.clone({
    setHeaders: {
      'my-auth-token': `${this.getSessionToken()}`
    }
  });
  return next.handle(request).pipe(
    catchError((response: HttpErrorResponse) => {
      if (response.status === 401) {
        // Call logout, but throttled!
        this.throttleLogout.next();
      }
      return throwError(response);
    }
  );
}
```
In the class constructor, we've initialized a simple `void` Subject which is piped through a `throttleTime` - once the first `401` is intercepted, the user is logged out and they aren't logged out again (due to `401`) for another five seconds.

In the next post, I'll show you how we wrote unit tests to verify all this functionality.