Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Creating a custom HttpInterceptor to handle 'withCredentials' in Angular 6+


:P
On this page:
Edit this Post

Been back at doing some Angular stuff after a long hiatus and I'm writing up a few issues that I ran into while updating some older projects over the last couple of days. I'm writing down the resolutions for my own future reference in a few short posts.

For this post, I needed to create and hook up a custom HttpInterceptor in Angular 6. There's lots of information from previous versions of Angular, but with the new HTTP subsystem in Angular 6, things changed once again so things work a little bit differently and that was one of the things that broke authentication in my application.

Use Case

In my use case I have a simple SPA application that relies on server side Cookie authentication. Basically the application calls a server side login screen which authenticates the user and sets a standard HTTP cookie. That cookie is passed down to the client and should be pushed back up to the server with each request.

WithCredentials - No Cookies for You!

This used to just work, but with added security functionality in newer browsers plus various frameworks clamping down on their security settings, XHR requests in Angular by default do not pass cookie information with each request. What this means is by default Angular doesn't pass Cookies captured on previous requests back to the server which effectively logs out the user.

In order for that to work the HttpClient has to set the withCredentials option.

return this.httpClient.get<Album[]>(this.config.urls.url("albums"),{ withCredentials: true })
                    .pipe(
                        map(albumList => this.albumList = albumList),
                        catchError( new ErrorInfo().parseObservableResponseError)
                    );

It's simple enough to do, but... it's a bit messy and more importantly, it's easy to forget to add the header explicitly. And once you forget it in one place the cookie isn't passed, and subsequent requests then don't get it back. In most application that use authentication this way - or even when using bearer tokens - you need to essentially pass the cookie or token on every request and adding it to each and every HTTP request is not very maintainable.

CORS - Allow-Origin-With-Credentials

In addition to the client side withCredentials header, if you are going cross domain also make sure that the Allow-Origin-With-Credentials header is set on the server. If this header is not set the client side withCredentials also has no effect on cross-domain calls causing cookies and auth headers to not be sent.

HttpInterceptor to intercept every Requests

To help with this problem, Angular has the concept of an HttpInterceptor that you can register and that can then intercept every request and inject custom headers or tokens and other request information.

There are two things that need to be done:

  • Create the HttpInterceptor class
  • Hook it up in the AppModule as a Provider configuration

Creating an HttpInterceptor

Creating the Interceptor involves subclassing the HttpInterceptor class so I create a custom class HttpRequestInterceptor.ts:

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

import { Observable } from 'rxjs';

/** Inject With Credentials into the request */
@Injectable()
export class HttpRequestInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler):
    Observable<HttpEvent<any>> {
    
      // console.log("interceptor: " + req.url);
      req = req.clone({
        withCredentials: true
      });
      
      return next.handle(req);
  }
}

This is some nasty code if you had to remember it from scratch, but luckily most of this boilerplate code comes from the Angular docs. What we want here is to the set the request's withCredentials property, but that property happens to be read-only so you can't change it directly. Instead you have to explicitly clone the request object and explicitly apply the withCredentials property in the clone operation.

Nasty - all of that, but it works.

Hooking up the Interceptor

To hook up the interceptor open up app.module.ts and assign the interceptor to the providers section.

Make sure to import the HTTP_INTERCEPTORS at the top:

import {HttpClientModule, HTTP_INTERCEPTORS} from '@angular/common/http';   // use this

and then add the interceptor(s) to the providers section:

providers: [            
    // Http Interceptor(s) -  adds with Client Credentials
    [
        { provide: HTTP_INTERCEPTORS, useClass: HttpRequestInterceptor, multi: true }
    ],
],

Summary

Customizing every HTTP request is almost a requirement for every client side application that deals with any kind of authentication. Nobody wants to send the same headers or config info on every request, and if later on it turns out there are additional items that need to be sent you get to scour your app and try to find each place the HttpClient is used which is not cool.

Creating one or more interceptors is useful for handling and creating standardized requests that fire on every request. In this example I added additional headers to every request, but you can potentially look at each url and decide what needs to be handled. The control is there as one or multiple central interception points to HTTP requests.

In the end this is relatively easy to hook up, but man is this some ugly, ugly code and good luck trying to remember the class salad - or even finding it. That's why I'm writing this up if for nothing else than my own sanity so i can find it next time. Maybe it's useful to some of you as well.

this post created and published with Markdown Monster
Posted in Angular  

 

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2019