8

I am in the process of building a new app that requires authorization headers. Typically I use something very similar to the approach found in this scotch.io article. But it has come to my attention that HTTP Interceptors are now fully supported within the Angular 4 ecosystem via the new HttpClientModule and I am trying to find some documentation on how exactly to use them.

If I am incorrect in (as of 4.3) this being the best practice for injecting authorization headers, I'd also be open to suggestions. My thinking was that it was a feature added recently which means there is probably good reason to migrate to an "Angular Approved" method.

joshrathke
  • 7,564
  • 7
  • 23
  • 38

3 Answers3

10

This answer is borrowing from the official documentation linked to by CodeWarrior.

Angular allows you to create an HttpInterceptor:

import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';

@Injectable()
export class NoopInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req);
  }
}

which you can then integrate into your app like so:

import {NgModule} from '@angular/core';
import {HTTP_INTERCEPTORS} from '@angular/common/http';

@NgModule({
  providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: NoopInterceptor,
    multi: true,
  }],
})
export class AppModule {}

To add an authorization header, you can clone the request with the changed headers:

import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private auth: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Get the auth header from the service.
    const authHeader = this.auth.getAuthorizationHeader();
    // Clone the request to add the new header.
    const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
    // Pass on the cloned request instead of the original request.
    return next.handle(authReq);
  }
}

Note that the interceptors act like a chain, so you can set up multiple interceptors to perform different tasks.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
3

Injecting AuthService to the Interceptor's constructor was giving me this error:

Uncaught Error: Provider parse errors: Cannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS ("[ERROR ->]"): in NgModule AppModule in ./AppModule@-1:-1

So instead of injecting it to the constructor, I used Injector of @angular/core and it worked fine. I am storing the token in localStorage and using basic auth. I need to set

Authorization: 'Bearer token_string'

Here is how I have implemented:

token.interceptor.ts

import {Injectable, Injector} from '@angular/core';

import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
import {AuthService} from './auth.service';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

    constructor(private injector: Injector) { }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

        const auth = this.injector.get(AuthService);
        if (auth.getToken()) {
            request = request.clone({
                setHeaders: {
                    Authorization: `Bearer ${auth.getToken()}`
                }
            });

        }

        return next.handle(request);
    }
}

getToken function in AuthService

Here you can implement the whole logic to get the header or only the token. Here in my case, I am only calling this to get the JWT token string.

/**
 * Get jwt token
 * @returns {string}
 */
getToken(): string {
    return localStorage.getItem('token');
}

app.module.ts

Import the TokenInterceptor

import {TokenInterceptor} from './pathToTheFile/token.interceptor';

add the following under @NgModule in providers: array.

providers: [
    {
        provide: HTTP_INTERCEPTORS,
        useClass: TokenInterceptor,
        multi: true
    }
    //, other providers
]
Lahar Shah
  • 7,032
  • 4
  • 31
  • 39
0

The problem I had with the recommended approach, was that the interceptors had to be known at compile time and, apparently, all in the same module.

I opted for implementing one interceptor and a chain of handler functions which can be extended at runtime. The service's intercept method takes care of the next() logic.

The service (basic code, no validations or maintenance):

export type HttpHandlerFunction = (req: HttpRequest<any>) => HttpRequest<any>;

@Injectable()
export class HttpInterceptorService implements HttpInterceptor {
    private _handlers: Array<HttpHandlerFunction> = [];

    addHandler(handler: HttpHandlerFunction): void {
        this._handlers.push(handler);
    }

    intercept(req: HttpRequest<any>, next: HttpHandler):
        Observable<HttpEvent<any>> {
            this._handlers.forEach((handler: HttpHandlerFunction) => {
                req = handler(req);
            })
        return next.handle(req);
    }
}

And the usage, in some service in a different module:

constructor(injector: Injector) {

    const interceptorsArray: Array<any> = injector.get(HTTP_INTERCEPTORS),
        interceptor: HttpInterceptorService = interceptorsArray &&
            interceptorsArray.filter((i: any) => i instanceof HttpInterceptorService)[0];
    if (interceptor) {
        interceptor.addHandler((req: HttpRequest<any>) => {
            const accessToken = this.getAccessToken();
            if (accessToken) {
                // doesn't work with direct headers.set, you must clone
                req = req.clone({ headers: req.headers.set('Authorization', accessToken) });
            }
        return req;
    });
}
dfl
  • 1,089
  • 8
  • 8