Tel Map

Blog

Angular2 Http Authentication Interceptor

In my Angular2 application I want to be redirected to the login page whenever I get a 401 response during an Ajax call. Therefore I want to intercept all Ajax calls and check for the response code. In addition to that I also want to set a couple of default request headers for each Ajax call. The code below shows an interceptor for angular2 http requests.

 Angular2 Http Interceptor using Typescript

Updated 05/16/16: Angular2 RC1 – All packages moved to @angular instead of angular2

In Angular2 it is possible to provide a different default implementation for a particular class.

You can provide these “new” default implementations by providing the implementations in the second parameter of the bootstrap function e.g.

This tells Angular to use the HashLocationStrategy class as default implementation for LocationStrategy.

I simply want to provide a new implementation for the Http class, which intercepts all calls and checks if the http status code is 401. Since Http needs a couple of parameters from the dependency injection I had to provide a factory function for my class.

I extended the original Http class, overrode alls request methods and checked for the response code. The original constructor of Http only has 2 parameters. I simply added another one to Router in order to make a redirect in case of a 401 response code.

Each request function is wrapped by a call to an interceptor function e.g. for the get method:

In the interceptor function itself, I check for the response code and do a redirect to the login page if the user is not authenticated.

In my example I specifically excluded the login request url from that check. When I submit a wrong username password combination I also get a 401 in return.

In the getRequestOptionArgs I also set ‘Content-Type’ to ‘application/json’ for each request. Since I am working on a single page application all my requests will be in json.

Summary

This article shows how a Http request interceptor can be implemented in Angular2. If you found a simpler way of doing this, please let me know.

 

 

47 Replies to “Angular2 Http Authentication Interceptor”

  1. Mark Magyarodi

    By subclassing the Http service you introduce a more complex provide (using factory). I make a normal wrapper service around Http with the same method signature (don’t even need to be same), where Http and Router are injected into. Instead of the super calls you write this._http, but everything would be the same.

    1. Darbio

      This is how I do it too. It’s much less complicated… Are there any advantages to doing it the way shown in this post?

  2. lindner Post author

    Many thanks for your reply. Nice to hear that your are doing it very similarly. I like the idea of replacing the default implementation of Http, because let’s say you already started implementing, then you do not have to replace Http with a new service within the whole project and if you are working on a project with multiple developers most likely someone will forget to use the new Http service. I totally understand however, that how I do it introduces some complexity in the provide function.

  3. Lexon

    Thanks for your article, but always I get an exception in es6-promise.js: catch is not a function

    So I use instead of catch the subscribe methode, like

    var response = super.request(url, options);
    response.subscribe(
    data => “”,
    err => this.forwardToLogin()
    );

    What do you think of this solution?

  4. lindner Post author

    Hi Lexon,

    I also had some problems regarding Observable.empty. In Visual Studio this line is marked as incorrect. Make sure to use the latest Typescript compiler and update angular, rxjs etc. to the latest version. I for example switched to Visual Studio Code for editing Typescript.

    If you are using subscribe in the interceptor example, it will probably not work as I wanted it to work. Let’s say I’m using my interceptor in a service to call a REST api. Then in this service I use the subscribe method and want to have the following behavior:

    a) I want to be redirected to the login page if I’m not signed in
    b) In the error handling of the subscribe method of the service, I do not want to receive 401 errors, but all other errors like internal server errors.

    So authentication errors should be completely swallowed and subsequent subscribe methods should not be called on receiving such an error. I think if you do it like in your example, you will still receive 401 errors in other places if someone else also subscribes to the result of the request.

    Best regards,
    Sebastian

  5. Lexon

    Hey Sebastian,

    Thanks for your response!

    You’re absolutely right – the subscribe methode is not the right here. It creates a second request.

    I also work with VS Code, because I have problems with typescript in VS. Now I have updated all my NPM packages (angular2, …) to the last version, but I have still the problem with the catch methode.

    I provide a simple project, where you could reproduce the problem: https://github.com/lexon0011/IssueDemonstrator

    In the repository you find a short description (Issue 2: HTTP Interceptor), where I describe the problem.

    Could you even take a look on it? Maybe you see the problem??

    Thanks!!!

  6. Lexon

    I found already a solution. I have to import following modules:

    import “rxjs/add/operator/catch”;
    import “rxjs/add/observable/throw”;
    import “rxjs/add/observable/empty”;

  7. Martin

    Thanks a lot for this post, the RxJS catch function turns out to be exactly what I was looking for. However I do agree with Mark Magyarodi on the idea that wrapping it would be a better idea. Also thanks to Lexon for the import of the necessary files, turns out I had to include those as well.

  8. Kamran

    Hello,

    in the example above, we are catching the errors incase of exception.

    Please confirm how to log something in case of success.

    1. lindner Post author

      Of course you can also put arbitrary log code into the intercept function. Instead of using “catch” you can also use the “do” or “subscribe” to achieve that. You might want to consider to read up on “hot and cold” Observables before doing so, however. Otherwise your logging functionality might be called multiple times.

      1. Kamran

        you mean like this:

        intercept(observable: Observable): Observable {

        observable.catch((err, source) => {
        console.log(‘HttpInterceptor Error:::’ + err.Message);
        if (err.status == 401 && err.url.search(‘/login’) == -1) {
        this._router.navigate([‘Login’]);
        return Observable.empty();
        } else {
        return Observable.throw(err);
        }
        });

        observable.do(
        x => this.refreshToken(),
        ex => console.log(“onError:: {0}”, ex.Message),
        () => console.log(“onCompleted”));

        return observable.map(res => res.json())
        }

        1. lindner Post author

          Hi Kamran,

          yes I think something like this might be what you are looking for. I cannot check if your example is fully working, however. Do you have any particular problems with your approach?

  9. Is it possible to inject your own services into this as well? I would like to use an external service for emitting signals when an HTTP request is complete, however the service remains “undefined”. Here is a snap shot of what my code looks like

    main.ts
    provide(Http, {
    useFactory: (xhrBackend: XHRBackend, requestOptions: RequestOptions, router: Router, _appEventService: AppEventService) => new HttpInterceptor(xhrBackend, requestOptions, router, _appEventService),
    deps: [XHRBackend, RequestOptions, Router]
    })

    HttpInterceptor.ts
    constructor(private backend: ConnectionBackend,
    private defaultOptions: RequestOptions,
    private _router: Router,
    private _appEventService: AppEventService) {

    super(backend, defaultOptions);
    }

    intercept(observable: Observable): Observable {
    return observable.finally(() => { console.log(this._appEventService); });
    }

    Console:
    this_appEventService == undefined

    1. Well this is embarrassing. I solved my issue by adding AppEventService to the deps in main.ts like so:
      provide(Http, {
      useFactory: (xhrBackend: XHRBackend, requestOptions: RequestOptions, router: Router, _appEventService: AppEventService) => new HttpInterceptor(xhrBackend, requestOptions, router, _appEventService),
      deps: [XHRBackend, RequestOptions, Router, AppEventService]
      })

      In anycase, thanks for the great blog, it really helped me out!

  10. Michael

    Hi,

    your code is great, but I get an error in the console “Cannot find default outlet” when injecting the router.
    When I remove “Router” from “deps” there is no error, but also no router (undefined).

    Does anyone else have this problem and even better a solution for it?

    1. lindner Post author

      Hi Michael,

      how did you set up your routes? Is routing working for you in general?
      Would be nice if you could post some code of you setup.

  11. Michael

    I found the error.
    In my login-component I imported http and HTTP_PROVIDERS. But I didn’t set the HTTP_PROVIDERS in the @Component -> providers. This has led to the error about the not found outlet. I always searched for problems with the router, not with http.

    I think, sometimes you have to ask someone else, before you can find the answer by yourself.

    Thanks a lot.

    Michael

  12. Michael

    I’m sorry, but I have the next problem after setting “providers:[HTTP_PROVIDERS]” in my login-component.
    Now the HttpInterceptor won’t be used.
    I just started a new angular-cli project, generated a login-route and added your code to main.ts. Then I import Http and HTTP_PROVIDERS in login.component.ts and have a function to do a http-post to my api-server after pressing a button:
    this.http.post(“login.php” , JSON.stringify({test:”true”})).map(res => {return res.json();}).subscribe(data => {alert(“ok”);})

    I get the alert “ok”, but the HttpInterceptor was never user. Wheter for setting Content-Type-Header, nor for checking the response.

    Any idea?

    Thank you.

    1. lindner Post author

      Did you include this part for the http interceptor setup in your code?

      bootstrap(MyApp, [
      HTTP_PROVIDERS,
      ROUTER_PROVIDERS,
      provide(LocationStrategy, { useClass: HashLocationStrategy }),
      provide(Http, {
      useFactory: (xhrBackend: XHRBackend, requestOptions: RequestOptions, router: Router) => new HttpInterceptor(xhrBackend, requestOptions, router),
      deps: [XHRBackend, RequestOptions, Router]
      })
      ])

      1. Michael

        Yes, I included the whole bootstrap including http-interceptor.
        Thank you for your help, but I switched to AuthHttp from Auth0 and extended it for my needs. This works fine now.

  13. Tim

    Hi, great article. I’m trying to set this up in RC3 and can’t seem to get the HttpInterceptor class to kick in when making a standard http.post(..) request from within a service class. Would you mind sharing code showing the consumer side of the equation?

    1. lindner Post author

      Hi,

      thanks! The client code is actually the same as if you do not use the HttpInterceptor. Here is an example, anyway.

      import {Injectable} from ‘@angular/core’;
      import {Http, Response} from ‘@angular/http’;
      import {GalleriesDto} from ‘../dtos/galleriesDto’;
      import {Observable} from ‘rxjs/Observable’;

      @Injectable()
      export class GalleryService {
      constructor(private http: Http) { }

      private _galleryUrl: string = ‘api/gallery’;

      getGalleries(skip :number, top: number) : Observable {
      return this.http.post(this._galleryUrl,
      JSON.stringify({ Skip: skip, Top: top }))
      .map(res =>
      res.json());
      }

      }

      I hope this helps.

    1. lindner Post author

      Hi Andrew,

      I did not set up a running example on plnkr. All the code you need is shown in the blog post anyway. The client code is exactly the same as if you would not use the HttpInterceptor.
      I also think authentication problems are hard to demo on plnkr. If you have a concrete question, however, I’m happy to help you out.

      Best,
      Sebastian

  14. Andriy Tolstoy

    Thanks for the article! Does it make sense to get an original observable back instead of creating a new one: so “return observable;” vs. “return Observable.throw(err);”?

    1. lindner Post author

      If you return the original observable all subscribers would also be notified if there is an authentication error. This is exactly what I tried to prevent.

  15. nbl

    when I tried to register in my app.module I got an error “cannot find the name ‘provide’ ” so I removed that registration from the app.module

    Now I am getting an error in the component that I am injecting this service into. The error is “No provider for ConnectionBackend!” This is probably due to it not finding the registration of the required components.

    Where and how do I register? Basically where to put these lines of code in Angular2 RC5

    bootstrap(MyApp, [
    HTTP_PROVIDERS,
    ROUTER_PROVIDERS,
    provide(LocationStrategy, { useClass: HashLocationStrategy }),
    provide(Http, {
    useFactory: (xhrBackend: XHRBackend, requestOptions: RequestOptions, router: Router) => new HttpInterceptor(xhrBackend, requestOptions, router),
    deps: [XHRBackend, RequestOptions, Router]
    })
    ])
    .catch(err => console.error(err));

    1. jb

      maybe you can do like this
      @NgModule({
      declarations: [
      AppComponent, CrisisCenterComponent, MapComponent
      ],
      imports: [
      BrowserModule,
      FormsModule,
      HttpModule,
      InMemoryWebApiModule.forRoot(HeroData),
      routing
      ],
      providers: [{ provide: Http, useClass: CustomHttp }],
      bootstrap: [AppComponent],
      })
      export class AppModule {}

    2. JP

      In Angular2 Final, you want to set the provider in the your app module.

      providers: [
      { provide: Http, useFactory: (backend: XHRBackend, options: RequestOptions) => {
      return new HttpInterceptor(backend, options);
      }, deps: [XHRBackend, RequestOptions]
      }
      ],

  16. Willem

    How can the same be achieved in Angular 2.0.1?
    The new router is no longer injectable in a global service (like an HttpInterceptor) before a first component is bootstrapped.
    I would still like to redirect with the Angular router instead of setting window.location.href.

    I believe route guards are not a complete solution, because they only check routes and don’t check every backend request. So a combination of route guards and something like an HttpInterceptor would be the best solution.

  17. Daniel Lichtenberger

    Thanks for the code! Still works for me with Angular 2.0.2.

    One issue I ran into is with the use of Observable.empty(). If you use .toPromise() on the result of the Http call, the promise will still be resolved with an empty (null) value. So for example in

    http.get(…).toPromise().then(response => this.field = response.json())

    the promise still be resolved in case a 401 happens, but with an empty (null) response. If that’s not what you want, you can return Observable.never() instead.

  18. squadwuschel

    Could you update your example to the current Angular Release 2.1 and perhaps provide a complete Gist or Github Repo with a simple example. I can’t get the hole thing to work.

  19. Rohit

    providers: [
    myTokenService,
    {provide: Http, useFactory: (backend: ConnectionBackend, defaultOptions: RequestOptions, router: Router, myToken: myTokenService) => {
    return new HttpInterceptorService(backend, defaultOptions, router, myToken);
    }, deps: [XHRBackend, RequestOptions, Router, myTokenService]
    },
    ]

    Note : myTokenService is using Http service.

    I am getting “Provider parse error”.

  20. Manoj

    I’m using requestOptions instead of defaultOptions, though don’t know if that would solve your issue or not ?
    {provide: Http, useClass: HttpInterceptor,
    useFactory: (xhrBackend: XHRBackend, requestOptions: RequestOptions, router: Router) =>
    new HttpInterceptor(xhrBackend, requestOptions, router),
    deps: [XHRBackend, RequestOptions, Router]}

  21. Reza

    Hi,

    I am using this, but I need to add a token to header, which is get from another service that returns another Obseravble,

    assume it as below

    getRequestOptionArgs(options?: RequestOptionsArgs) : RequestOptionsArgs {
    if (options == null) {
    options = new RequestOptions();
    }
    if (options.headers == null) {
    options.headers = new Headers();
    }

    this.tokenService.getToken().subscribe(token => {
    options.headers.append(‘token’, token);
    });
    options.headers.append(‘Content-Type’, ‘application/json’);
    return options;
    }

    but since getToken is async the method doesn’t wait to get token,

    then I used it as below

    get(url: string, options?: RequestOptionsArgs): Observable {
    return this.tokenService.getToken().flatMap(token =>
    {
    //add token to header
    return this.intercept(super.get(url,options));
    });
    }

    based on documentations of flatMap it should chain the obseravbles, but the result is like no body subscribe to getToken and it and super doesn’t call at all.

    Do you know what is my mistake?

    Thanks is advanced

  22. Joe Sleiman

    hi ,
    i’m using same service (copy -past)
    but instead of bootstrap function : i put in the provide function in app.module.ts:
    {provide: LocationStrategy, useClass: HashLocationStrategy },
    {provide: Http, useFactory: (xhrBackend: XHRBackend, requestOptions: RequestOptions) => new HttpInterceptor(xhrBackend, requestOptions),deps: [XHRBackend, RequestOptions]}
    without the catch method,
    when i got 401 && url != /login ===> the function enter in the first if to navigate to login page but suddenly repeat (it go above) to if condition (it found here that error.status = undefined) than enter in else and return Observable.throw(err);
    so i can’t redirect to loginpage
    can you help me.

  23. Sahil Purav

    It’s not working in Angular 4. It says, Argument of type ‘Observable’ is not assignable to parameter of type ‘Observable’.

    Any idea what is going wrong here?

Leave a Reply

Your email address will not be published. Required fields are marked *