Use a configured fetch client for authorized requests in Aurelia

When working with a SPA framework like Aurelia you will make a lot of HTTP request to a backend, probably in the form of a (REST) API. In order to authenticate on the API you have to provide a Bearer Token in the Authorize header of each request. You could do this like this:

import { inject } from 'aurelia-framework';
import { Order } from './order'
@inject(HttpClient)
export class Orders {
    orders: Order[];
    private http: HttpClient
    constructor(httpClient: HttpClient) {
        this.http = httpClient;
        this.http.fetch('/api/Orders', {
            method: 'GET',
            headers: {
                'Authorization': 'Bearer ' + YOUR_BEARER_TOKEN,
                'Content-Type': 'application/json'
            }
        })
            .then(result =>; result.json() as Promise<Order[]>)
            .then(data =>; {
                this.orders = data;
            });
    }
}

If we follow this approach we would have to add the Authorization header every time we use the fetch method. This will get tedious fast, so it would be nice if we could find a way to only do this once. One way to achieve this is to configure Aurelia’s fetch client to always add an Authorization header (with the Bearer Token) and make sure this configured client get’s used in all requests made to the API.

Configure the fetch client

The fetch client in Aurelia has a configure method that enables us to provide it with a default configuration. An HttpClient instance can be configured with several options, such as default headers and the base URL to be used on requests to the API. The best place to do this is in the file that handles the configuration of Aurelia. Depending on the way you created your Aurelia project the configuration will be done in a file called something like main.js, main.ts or boot.ts. Here we can create a new HttpClient object and gave it a default configuration.

const http = new HttpClient().configure(config => {
    config
        .withBaseUrl("api/")
        .withDefaults({
            headers: {
                'Content-Type': 'application/json'
            }
        });
});

In this example we give it a base URL and add the Content-Type header.
Don’t be tempted to add the Authorization header to the headers in the withDefaults method because at the time Aurelia is being configured you will most likely not have acquired a token from your authorization endpoint yet. Luckily there is an Interceptor available that intercepts each fetch request being made with the fetch client. In the withInterceptor method we have access to the incoming request and can make changes to it. In our case we will add the Authorization header with the Bearer Token. Assuming a user has to log in to the application before using it, a Bearer token will be available when making requests to the API, and with this configuration in place the Authorization header (with the Bearer Token) will now be added to each request.

const http = new HttpClient().configure(config =>; {
    config
        .withBaseUrl("api/")
        .withDefaults({
            headers: {
                'Content-Type': 'application/json'
            }
        })
        .withInterceptor({
            request(request: Request) {
                request.headers.append('Authorization', 'Bearer ' + YOUR_BEARER_TOKEN);
                return request;
            }
        });
});

Configure the Dependency Injection container

We now have a nicely configured instance of HttpClient, but at the moment it’s only available during configuration of Aurelia and can’t be used in the rest of our application.
We would like to use this instance every time we inject the HttpClient to our classes. With Aurelia we can accomplish this through explicit configuration of it’s Dependency Injection (DI). The Aurelia instance that is also available during configuration has a container property which points to the root DI container for our application. We can add our configured instance of the HttpClient to Aurelia’s Dependency Injection container registry with the registerInstance method.

aurelia.container.registerInstance(HttpClient, http);

This will make sure the configured HttpClient will be used whenever an HttpClient instance is requested from the DI container. This will be the case when we decorate a class with the inject decorator @inject(HttpClient).

The complete file will now look something like this:

import { Aurelia, PLATFORM } from 'aurelia-framework';
import { HttpClient } from 'aurelia-fetch-client';
export function configure(aurelia: Aurelia) {
    aurelia.use.standardConfiguration();
    const http = new HttpClient().configure(config =&gt; {
        config
            .withBaseUrl("api/")
            .withDefaults({
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            .withInterceptor({
                request(request: Request) {
                    request.headers.append('Authorization', 'Bearer ' + YOUR_BEARER_TOKEN);
                    return request;
                }
            });
    });
    aurelia.container.registerInstance(HttpClient, http);
    aurelia.start().then(() =&gt; aurelia.setRoot(PLATFORM.moduleName('app')));
}

With this in place the code that uses the fetch client to make a request to the API can now be simplified to:

import { HttpClient, json } from 'aurelia-fetch-client';
import { inject } from 'aurelia-framework';
import { Order } from './order'
@inject(HttpClient)
export class Orders {
    orders: Order[];
    private http: HttpClient
    constructor(httpClient: HttpClient) {
        this.http = httpClient;
        this.http.fetch('/api/Orders')
            .then(result => result.json() as Promise<Order[]>)
            .then(data => {
                this.orders = data;
            });
    }
}

Conclusion

We can prevent writing duplicate code in our request by configuring Aurelia’s DI container and HttpClient and as a result have better readable and more concise code.

comments powered by Disqus