Overview

It’s become pretty commonplace to authenticate and authorize web clients using JWTs (JSON Web Tokens) via HTTP request headers; it’s also become fairly popular to leverage some form of state management in web clients with a flavor of Redux. In this post I’ve created a simple web client with Angular CLI and NGRX that demonstrates how to retrieve a saved JWT from a NGRX store and apply it to all API requests using an HTTP interceptor.

If you want to skip the quick explanation and just start playing with the app, head on over to the GitHub repo for the application.

Series Table of Contents

  1. Add JWT Token to Angular HTTP Requests Using NGRX

  2. Angular 7 Login and Registration with JWT Node Authentication

  3. Angular 7 Authenticated Route Guard

Assumptions

This post assumes familiarity with Angular and Angular CLI, TypeScript, JWTs, RxJS, NGRX (or possibly Redux), Smart Container and Dumb / Presentation Component and json-server.

At the time of writing this my system leveraged the following versions:

  • Node.js: 10.14.2

  • NPM: 6.4.1

  • Angular: 7.0.3

  • Angular CLI: 7.0.5

  • OS: MacOS High Sierra v10.13.6

The rest of the versions can easily be looked up in the package.json.

Full disclosure: Code is not production grade, it is meant for the explanation purpose only.

We’ll keep this example focussed on the client and assume there’s an authentication API that’s already provided the web client a JWT. To that end, we’ll generate a JWT and simply hardcode the value in our NGX store instead of actually hitting a login API that returns a valid token. The following JWT was generated using the jwt.io Debugger:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkJyaWFuIFJpbGV5IiwiaWF0IjoxNTE2MjM5MDIyfQ.Ke1LOKC-5RWHDNCowA8HAvraGgmGwDb5VbxQlAg2j8o

If you open up the auth.reducer.ts you’ll see this token hard-coded in the initial state.

Quick Start

Let’s clone the repo and fire up the app so we can see it in action. Open up a terminal and enter the following commands:

wasi$ git clone https://github.com/webappsolution/angular-add-auth-token-ngrx.git
wasi$ git checkout feature/step-0-base-project
wasi$ npm i
wasi$ npm run dev

The last command will concurrently start both the server and client — the server uses json-server to quickly scaffold an in-memory JSON database, while the client is the Angular app running via Angular CLI. Your terminal or CLI client should resemble the following:

angular-add-auth-token-ngrx-cli-npm-run-dev.png

Next navigate to http://localhost:4300/ to see the application in action. It should display a simple list of beers:

angular-add-auth-token-ngrx-ui.png

Proof is in the Pudding

Now that we have the app up and running, let’s talk about what’s going on and finally demonstrate adding the token to each API request.

As stated in the assumptions, the app doesn’t have a login screen and we’ll just assume the user has logged in successfully and the API has returned a valid JWT that we’ve stored in our NGRX store. We’ll also assume that the endpoint to load beers is secure and requires a valid JWT, so we’ll need to add the JWT to the API request. Open up Chrome DevTools and using the Network tab you should see a request to the beers endpoint with the token added to the header like below.

angular-add-auth-token-ngrx-network-auth-header.png

So how do we add the NGRX store token to the header on each API request without individually adding it to each service request?

HTTP Interceptors

Angular provides developers a means to intercept both HTTP requests and responses via HttpInterceptors. There are 2 keys to using interceptors:

  1. Interceptors must implement the interface HttpInterceptor by providing a concrete implementation of the method: public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>

  2. Custom interceptors need to be added to your application as providers using the base interceptor token HTTP_INTERCEPTORS.

Let’s get to the heart of it and look at the AddTokenHeaderHttpRequestInterceptor.

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { select, Store } from "@ngrx/store";
import { EMPTY, Observable, of } from "rxjs";
import { first, mergeMap } from "rxjs/operators";
import * as fromState from "../state";

@Injectable()
export class AddTokenHeaderHttpRequestInterceptor implements HttpInterceptor {
    /**
     * Constructor.
     */
    constructor(private store$: Store<any>) {}

    /**
     * Intercepts all HTTP requests and adds the JWT token to the request's header if the URL
     * is a REST endpoint and not login or logout.
     */
    public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // Consider only adding the auth header to API requests as this will add it to all HTTP requests.
        return this.addToken(request).pipe(
            first(),
            mergeMap((requestWithToken: HttpRequest<any>) => next.handle(requestWithToken))
        );
    }

    /**
     * Adds the JWT token to the request's header.
     */
    private addToken(request: HttpRequest<any>): Observable<HttpRequest<any>> {
        // NOTE: DO NOT try to immediately setup this selector in the constructor or as an assignment in a
        // class member variable as there's no stores available when this interceptor fires fires up and
        // as a result it'll throw a runtime error.
        return this.store$.pipe(
            select(fromState.getToken),
            first(),
            mergeMap((token: string) => {
                if (token) {
                    request = request.clone({
                        headers: request.headers.set("Authorization", `Bearer ${token}`),
                        withCredentials: true
                    });
                } else {
                    console.warn(`Invalid token!!! Cannot use token "${token}".`);
                }
                return of(request);
            })
        );
    }
}

The first thing we need to do is intercept the HTTP request followed by adding the JWT to the request as a header. As mentioned previously, to intercept the request one only needs to implement the intercept() method. Here we could apply some simple filtering logic that only adds the auth header to actual API requests as the default interceptor will catch all HTTP requests — this means it’ll add the auth header to API requests as well as say images, CSS, JS, etc (which you may/not want to do). But to keep things simple we’ll add it to all.

Next we’ll add the auth header via the method addToken() using a NGRX selector function and then flatten the nested observable so it returns a new observable of the request with the token added to the header, hence the use of the RxJS higher order observable mergeMap() aka flatMap(). Finally, we’ll use the first() operator so we don’t fire this every time the token value changes in the store and only when an actual request is made.

Finally, we need to add the custom interceptor to our application. Open up HttpInterceptorModule and you’ll see how:

import { HTTP_INTERCEPTORS } from "@angular/common/http";
import { NgModule } from "@angular/core";
import { AddTokenHeaderHttpRequestInterceptor } from "./add-token-header.http-request-interceptor";

const PROVIDERS = [
    {
        provide: HTTP_INTERCEPTORS,
        useClass: AddTokenHeaderHttpRequestInterceptor,
        multi: true
    }
];

@NgModule({
    imports: [],
    exports: [],
    declarations: [],
    providers: PROVIDERS
})
export class HttpInterceptorModule {}

The key here is to add our custom interceptor to the providers and setting the property multi to true to indicate that we could have more than one HTTP interceptor implementation.

Conclusion

In this quick tutorial we saw how to add a token to all API requests via an HTTP request interceptor that pulls from a NGRX store. Key points to the example are:

  1. Implement a custom, concrete HTTP interceptor: export class AddTokenHeaderHttpRequestInterceptor implements HttpInterceptor

  2. Grab the token from the NGRX store using a selector and return a new observable with the token added to the request as a header.

  3. Add the custom interceptor to your application via a module’s provider list.

See the code in action by grabbing the GitHub repo for the application.

Comment