Angular: Cyclic dependency error with HttpInterceptor

Created on 19 Jul 2017  ·  88Comments  ·  Source: angular/angular

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

Trying to do the same example of AuthInterceptor like in official doc, to add a Authorization header on all requests.

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

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const authHeader = this.auth.getAuthorizationHeader();
    const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
    return next.handle(authReq);
  }
}

Problem : with real code, the AuthService needs to request the server on login, signin, etc., so it itself inject HttpClient.

@Injectable()
export class AuthService {

  constructor(private http: HttpClient) {}

}

It creates this fatal error in console :

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

Expected behavior

Should work, as it's a very common case. Otherwise interceptors are limited to use services without Http.

Environment


Angular version: 4.3.0
commohttp high bufix

Most helpful comment

I resolved simply not setting authService in constructor but getting in the intercept function.


  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Get the auth header from the service.
    const auth = this.inj.get(AuthenticationService);
    const authToken = auth.getAuthorizationToken();
    ...
   }

All 88 comments

Will it work like this?

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

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const authHeader = this.auth.getAuthorizationHeader();
    const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
    return next.handle(authReq);
  }
}

@tytskyi I tried that and didn't work

  constructor(inj: Injector) {
    this.auth = inj.get(AuthService)
  }

works though

https://plnkr.co/edit/8CpyAUSJcCiRqdZmuvt9?p=preview

I confirm that @tytskyi solution doesn't work.

For info, for now I've separated in two services, one for login/signin/... (which uses HttpClient) and one just for managing the AuthToken (no Http).

But going further, I added a error handler to catch 401 errors (when the token is invalid). But in such case, I need to call logout, so I need the service with HttpClient, and thus I fall in the initial problem again, so it is very problematic.

This is a true cyclic dependency, and @Toxicable's example is the correct way to handle it.

However, I'd be concerned if the AuthService was making its own HTTP requests, and AuthInterceptor added the header to every request (as it's written) without checking the details of that request. That means that a request will go through the interceptor, which goes to the auth service, which makes a request, which goes through the interceptor, and it recurses infinitely.

No, the AuthService contains methods which need to do some HTTP requests (like login), but the AuthInterceptor does _not_ use them, it just needs the AuthService to get the token.

My code is very similar to @cyrilletuzi, I tried @Toxicable solution, I have no more cyclic dependency but I get:

ERROR Error: Uncaught (in promise): RangeError: Maximum call stack size exceeded
RangeError: Maximum call stack size exceeded
    at _createProviderInstance$1 (core.es5.js:9480)
    ...   

I use HttpClient and I create this interceptor to add the jwt token. Everyting work perfect but I have a bad practise. I use Http inside the HttpClient Interceptor. If I change

private http: Http,
to
private http: HttpClient
I get this cycle error

Cannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS ("[ERROR ->]")
any ideas how can I make it work?

import {Injectable} from "@angular/core";
import {HttpEvent, HttpHandler, HttpInterceptor} from "@angular/common/http";
import {HttpRequest} from "@angular/common/http";
import {Observable} from "rxjs/Observable";
import {Http} from "@angular/http";
import {SiteService} from "../services/site.service";
import {Router} from "@angular/router";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

constructor(
    private http: Http,
    private router: Router,
    private siteService: SiteService
) {}

refreshToken() {
    return this.http.get(this.siteService.apiDomain() + '/api/token?token=' + localStorage.getItem('JWToken'), {})
        .map((response: any) => {
            let data = response.json();
            return {
                token: data.token,
                permissions: data.permissions,
                user: data.user,
            };
        })
}

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const clonedRequest = req.clone({
        headers: req.headers.set('Authorization', 'Bearer ' + localStorage.getItem('JWToken'))
    });

    return next.handle(clonedRequest).catch((res) => {

        if (res.status === 401 || res.status === 403) {
            return this.refreshToken().flatMap((data) => {
                if (data.token !== '') {
                    localStorage.setItem('currentUser', JSON.stringify(data.user));
                    localStorage.setItem('currentUserPermissions', JSON.stringify(data.permissions));
                    localStorage.setItem('JWToken', data.token);
                } else {
                    localStorage.removeItem('currentUser');
                    localStorage.removeItem('currentUserPermissions');
                    localStorage.removeItem('JWToken');
                    this.router.navigate(['./auth/login']);
                    return Observable.throw(res);
                }
                const clonedRequestRepeat = req.clone({
                    headers: req.headers.set('Authorization', 'Bearer ' + localStorage.getItem('JWToken'))
                });
                return next.handle(clonedRequestRepeat);
            })
        } else {
            return Observable.throw(res);
        }

    });

}
}

Another important thing for those who will use this interceptor for their project but irrelevant to the current problem is to set headers to the refresh token response at least some seconds.

->header('Cache-Control', 'public, max-age=45')
->header('Expires', date('D, d M Y H:i:s ', time() + 45).'GMT');

This is interesting,

I thought that having service like AuthService, which manages token accessibility and performs login and refresh requests it's a common use case.

So basically here is the scenario. We have AuthService which depends on HttpClient (obviously). It has few methods: getAccessToken, getRefreshToken, login, refreshSession and logout.

Also in addition we need to have AuthInterceptor, which will add token to the request, if token exists. And obviously it depends on AuthService, cause service has access to the token.

So here's the problem: HttpClient depends on HTTP_INTERCEPTORS (basically it's AuthInterceptor), AuthInterceptor depends on AuthService, and AuthService depends on HttpClient. And Cyclic error.

So this common use case is not possible in the current implementation.

But why angular team couldn't implement interceptors in the similar way how the first angular interceptors work? E.g. to have extra store service, using which one we can register new interceptors, e.g. httpInterceptors.push(interceptor)?

I wrote similar library called ng4-http, which implements similar approach to the first angular. It uses additional service which stores registered interceptors. And in this case we can inject any kind of services there and use them properly. Current library uses old http module.

So the question is, am I wrong? If yes, then where ?

I have code that is quite similar to the examples posted here, and I've been encountering the same issue. In fact, it seems extremely close to what @cyrilletuzi is trying to do, I'm just trying to get the value of a token property I have set in an AuthService, I'm not trying to make use of it any other way. Just access that in the interceptor. Adding the service to the constructor in the interceptor gives me the cyclic dependency error, basically what @mixalistzikas got, down to the message, while trying the solution @Toxicable provided seems to give me the same issue @perusopersonale got, i.e. a problem with too much recursion, and the app crashing.

Eventually just decided to access the token directly from session storage where I had it stored, rather than retrieve it from the service, but the fact that this issue seems to be occurring when there doesn't seem to be any real cyclic dependency seems off. The workaround I described works for now for me, but definitely many situations where you would need to access a service somewhere, and that doesn't seem possible in this manner

This example also works for me, but i worred because http in the future may be deprecated and also is not good for the app to inject 2 different libraries for the same thing.

If anyone find a solution, please answer...

As a temporary solution Auth token can be stored as a static prop on the AuthService class. Then you won't need to instantiate it.

You may also try a little hack to instatiate the service, like:

constructor(private injector: Injector) {
  setTimeout(() => {
    this.authService = this.injector.get(AuthService);
  })
}

This way you won't get the max call stack exceeded error.

I resolved simply not setting authService in constructor but getting in the intercept function.


  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Get the auth header from the service.
    const auth = this.inj.get(AuthenticationService);
    const authToken = auth.getAuthorizationToken();
    ...
   }

That's perfect @perusopersonale ..... You solve my problem...

That's the final interceptor with some logging functionality and keep loading status for loader animation

import {Injectable, Injector} from "@angular/core";
import {HttpEvent, HttpHandler, HttpInterceptor, HttpResponse} from "@angular/common/http";
import {HttpRequest} from "@angular/common/http";
import {Observable} from "rxjs/Observable";
import {SiteService} from "../services/site.service";
import {Router} from "@angular/router";
import {LoadingService} from "../../components/loading/loading.service";
import {AuthenticationService} from "../services/authentication.service";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    constructor(private router: Router,
                private siteService: SiteService,
                private loadingService: LoadingService,
                private injector: Injector) {
    }



    private fixUrl(url: string) {
        if (url.indexOf('http://') >= 0 || url.indexOf('https://') >= 0)
            return url;
        else
            return this.siteService.apiDomain() + url;
    }

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

        const clonedRequest = req.clone({
            headers: req.headers.set('Authorization', 'Bearer ' + localStorage.getItem('JWToken')),
            url: this.fixUrl(req.url)
        });

        let authenticationService = this.injector.get(AuthenticationService);

        this.loadingService.start();
        const started = Date.now();
        return next.handle(clonedRequest)
            .do(event => {
                if (event instanceof HttpResponse) {

                    const elapsed = Date.now() - started;
                    console.log('%c Request for ' + this.fixUrl(req.urlWithParams) + ' took ' + elapsed + ' ms.', 'background: #222; color: yellow');
                }
            })
            ._finally(() => {
                this.loadingService.stop();
            })
            .catch((res) => {
                if (res.status === 401 || res.status === 403) {
                    this.loadingService.start();
                    return authenticationService.refreshToken().flatMap((data: any) => {
                        this.loadingService.stop();
                        if (data.token !== '') {
                            localStorage.setItem('currentUser', JSON.stringify(data.user));
                            localStorage.setItem('currentUserPermissions', JSON.stringify(data.permissions));
                            localStorage.setItem('JWToken', data.token);
                        } else {
                            localStorage.removeItem('currentUser');
                            localStorage.removeItem('currentUserPermissions');
                            localStorage.removeItem('JWToken');
                            this.router.navigate(['./auth/login']);
                            return Observable.throw(res);
                        }
                        let clonedRequestRepeat = req.clone({
                            headers: req.headers.set('Authorization', 'Bearer ' + localStorage.getItem('JWToken')),
                            url: this.fixUrl(req.url)
                        });
                        return next.handle(clonedRequestRepeat).do(event => {
                            if (event instanceof HttpResponse) {

                                const elapsed = Date.now() - started;
                                console.log('%c Request for ' + req.urlWithParams + ' took ' + elapsed + ' ms.', 'background: #222; color: yellow');
                            }
                        });
                    })
                } else {
                    return Observable.throw(res);
                }

            });

    }
}

And don't forget...

Another important thing for those who will use this interceptor for their project is to set headers to the refresh token response at least some seconds.

->header('Cache-Control', 'public, max-age=45')
->header('Expires', date('D, d M Y H:i:s ', time() + 45).'GMT');

Also...
And this is my loadingService

```import { Injectable } from '@angular/core';

@Injectable()
export class LoadingService {

public count = 0;

constructor() { }

start(): void {
    this.count++;
}

stop(): void {
    this.count--;
}

}
```

Just to remind if someone does not know. RefreshToken can be retrieved only while main token is not expired. You can call token endpoint and post refresh token to obtain new accessToken (e.g. 1 hour before main token os expired).The problem is the following. In such case our method getAccessToken() will return observable result because it exposes http end obtaines new token using refresh token if necessary.
So how would you handle the following: handle cyclic dependency issue and inject observable to interceptor?

@perusopersonale that stack overflow error means that your circular dependency has been resolved _at build time_ but is still very much present at runtime, you're making a new request from within the interceptor which _also_ goes through the interceptor, and that repeats forever.

I suggest using next.handle() to make the dependent request instead of trying to get ahold of HttpClient there.

I want to inject a service for the purpose of getting a value stored out of the service, not to make a request, but that service does make use of the HttpClient to make requests. I don't think this issue should be closed.

@nazarkryp when using the manual injection method, I seem to be getting a different instance of the service. That could be my fault with how I set up the provider for the service (although I think I took care of only providing it once). Are you seeing the same thing?

I'm having the same issue, and seems there's still no satisfying answer provided above. This shouldn't be closed.

Agreed @will-copperleaf. I still view this as a design problem with HttpClient/Interceptors in general. Interceptors needing to make requests themselves is a fairly common case (particularly when they're being used for authentication, as I suspect is the case for most here). Injecting the Injector and requesting services ad-hoc seems a bit hacky.

Getting an Injector instance does not work for me.

Error:

AppComponent_Host.html:1 ERROR TypeError: Cannot set property 'injector' of undefined
    at AuthHttp (authhttp.interceptor.ts:10)
    at eval (module.ngfactory.js? [sm]:1)
    at _callFactory (core.es5.js:9550)
    at _createProviderInstance$1 (core.es5.js:9489)
    at resolveNgModuleDep (core.es5.js:9471)
    at _callFactory (core.es5.js:9550)
    at _createProviderInstance$1 (core.es5.js:9489)
    at resolveNgModuleDep (core.es5.js:9471)
    at _createClass (core.es5.js:9514)
    at _createProviderInstance$1 (core.es5.js:9486)

AuthHttp class:

1.  import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
2.  import { Injectable, Injector } from '@angular/core';
3.  import { Observable } from 'rxjs/Rx';
4.  import { AuthService } from './auth.service';
5.
6.  @Injectable()
7.  export class AuthHttp implements HttpInterceptor {
8.     constructor(private injector: Injector) { }
9.
10.    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
11.        return next.handle(req);
12.    }
13.  }

@will-copperleaf Which example have you tried?

You should be able to do this for example:

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

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  private authService: AuthService;

  constructor(
    private injector: Injector
  ) { }

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

    this.authService = this.injector.get(AuthService); // get it here within intercept

    const authRequest = request.clone({
      headers: request.headers.set('Authorization', this.authService.getHeaders())
    });

    return next.handle(authRequest);
  }

}

So basically if my authService needs to do an http request to retrieve both the token and the refresh token and then store those in localStorage, how would you propose to implement this?
I see in the above examples that people inside the intercept method add the headers from the authService or directly add the token from localStorage(which means you already have a token).
What if my getHeaders() method is asynchronous?

From what I understand, there is no way to exclude specific requests from the interceptors so that they don't get intercepted, there is no way to load the interceptors for a specific component or module (they have to be defined in the app module).

I use this solution in my auth.interceptor.ts file:

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

  if (req.url.includes('/token')) {
    return next.handle(req);
  }
  this.tokenService = this.injector.get(TokenService);
...
}

I did what @twoheaded did but for some reason it didn't work for me. My TokenService was passing HttpClient as one of it's dependencies inside it's constructor.

I made my TokenService depend on Http. That was the only solution that worked for me, although a bit hacky.

I get a feeling that this whole Interceptors concept is not really production-ready at the moment.

@twoheaded 's solution works for me and I do use HttpClient.

The same problem, continuing to be concerned

The setup below with @twoheaded 's solution works for me. You can log every step to see the logic behind. As I understand, this is where return next.handle(req) helps. Unless you return that, this whole thing is gonna loop infinitely. This is what @alxhub also suggested above.

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

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  private authService: AuthService;

  constructor(private inj: Injector) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const headers = {'Content-Type': 'application/json'};

    if (req.url.includes('/token-refresh')) {
      console.log("Refresh token request");
      return next.handle(req);
    }

    this.authService = this.inj.get(AuthService);

    // Example of the token-refresh call
    if (this.authService.getToken()) {
      this.authService.refreshToken().subscribe(
        res => console.log(res)
      )
    }
    const copiedReq = req.clone({setHeaders: headers});
    console.log("Last");
    return next.handle(copiedReq);
  }
}

Worth noting that if you need to send http request from the interceptor to get some data(i.e revalidation logic) and you don't want to use bare xhr/fetch you can always use class-interface HttpBackend to omit interceptors and send request that way. Plus it works when mocking backend for tests :smiley:

import { Inject, Injectable, InjectionToken } from '@angular/core';
import { HttpBackend } from '@angular/common/http';
import {
  HttpInterceptor,
  HttpEvent,
  HttpHandler,
  HttpRequest,
  HttpEventType,
  HttpErrorResponse,
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/retryWhen';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/of';

export const MY_REQUEST = new InjectionToken<HttpRequest<any>>('MY_REQUEST');

@Injectable()
export class MyInterceptor implements HttpInterceptor {
  constructor(
    @Inject(MY_REQUEST) private myRequest: HttpRequest<any>,
    private backend: HttpBackend
  ) {}

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    return next.handle(req).retryWhen(errors => {
      return errors.mergeMap((error: HttpEvent<any>) => {
        if (error instanceof HttpErrorResponse && error.status === 401) {
          return (
            this.backend
              .handle(this.myRequest)
              // Get response.
              .filter(event => event.type === HttpEventType.Response)
              // Retry on success.
              .mergeMap(() => Observable.of(null))
              // Stop and throw on error.
              .catch(() => Observable.throw(error))
          );
        }

        // Throw error otherwise.
        return Observable.throw(error);
      });
    });
  }
}

Using HttpHandler may be problematic in some cases for 3rd party library because user can add more interceptors which can modify your request.

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

when in using another service that has httpclient.

i just tried angular ^5.0.0-rc.2, the problem still exists :(((((((((((((((

@perusopersonale Worked flawlessly. Getting the auth service directly from the injector is a nice option.

I think HttpClient should have option to call HttpBackend directly, without any interceptors.
As additioanl feature it can have option to use custom interceptors.

What do you think about it?

@vadjs

I think HttpClient should have option to call HttpBackend directly, without any interceptors.

It can do this now - inject HttpBackend. Alternatively, it can use the next: HttpHandler to make calls without going through earlier interceptors in the chain.

@alxhub if it is already possible, maybe it should just be documented.
In the meanwhile, I've implemented my interceptor to refresh my authToken by using "fetch" ...

Having same issue injecting the router in the constructor:

@Injectable()
export class ForbiddenInterceptor implements HttpInterceptor {

constructor(private router:Router) {
}

intercept(req: HttpRequest, next: HttpHandler): Observable> {
return next.handle(req).do(event => {
console.log("interceptor invoked");
}, err => {
console.log("interceptor invoked error");

});

}
}

Cannot instantiate cyclic dependency! ApplicationRef ("[ERROR ->]"): in NgModule AppModule in ./AppModule@-1:-1

Need the router to redirect to login on a token expiration (HTTP 403). Injecting the router manually, e.g. injector.get(Router) didn't work (return undefined)

Okay fixed it by injecting the injector and adding a get method:

public get router(): Router {
return this.injector.get(Router);
}

Prior to Angular 4.3.0 unfortunately it's impossible to use Injector manually inside intercept method:

ERROR Error: Uncaught (in promise): RangeError: Maximum call stack size exceeded

But there is one more workaround using rxjs:

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return Observable.create((observer: any) => {
        setTimeout(() => {
            const authService = this.injector.get(AuthenticationService)
            observer.next(authService.getAuthorizationHeader())
            observer.complete()
        })
    })
        .mergeMap((Authorization: string) => {
            let authReq = req

            if (Authorization) {
                authReq = req.clone({
                    setHeaders: {
                        Authorization
                    }
                })
            }

            return next.handle(authReq)
        })
}

@olegdunkan "Prior to Angular 4.3.0"? I'm running 4.4.6 and I'm having the same issue. I use the workaround from @perusopersonale for now.

i added {deps:Http] in main module and it works for me, but i don't why is need

providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthTokenInterceptor,
multi: true,
deps: [Http]
}],

It worked on perfectly @perusopersonale. Thanks a million !

I've tried @perusopersonale 's solution and it doesn't work. I get the Maximum call stack size exceeded error. I'm running 5.0.0. Has anyone else got this to work in version 5?

@vlad-ursan Make sure you are injecting your AuthService in your Interceptor AND injecting HttpClient into your AuthService manually. Also double check you are using HttpClient and not the deprecated Http object. It will fail in 5.0.0 for both of those reasons.

Is it something that the Angular team is actually looking to fix so that people don't have to inject stuff into other stuff that are injected in some other stuff to circumvent the issue or is it somewhere at the bottom of a huge backlog of stuff that will most probably never be looked at, so we can resort to our own solutions to this for ever?
If the second thing is true, probably a note in the documentation that says that interceptors are not supposed to accept any async dependencies that depend on HttpClient would be helpful.

@vlad-ursan you meant you are getting the Maximum call stack size exceeded error, trying this?

screen shot 2017-11-14 at 12 58 03 pm

@rbj325 I'm actually using another service (ApiService) injected AuthService which uses `HttpClient. I'm not sure I know what you mean by inject it manually though.

@luillyfe In my interceptor I am using that exact same piece of code.

@mixalistzikas I've tried to use your interceptor, actually I've been using similar one but there is an issue if there are resolves for the route. Could you suggest me how to solve this one ? :( Your interceptor also doesn't process the second request in resolvers chain. https://stackoverflow.com/questions/47751566/angular-4-jwt-http-interceptor-doesnt-work-on-chain-requests If anyone else can help me with this I'll buy you a beer )))) Spent too much time on this

Hello Guys, I made a solution to work with refresh token and I posted it here: https://stackoverflow.com/questions/47797180/refresh-token-oauth-authentication-angular-4/47797278#47797278

Any feedback will be welcome.

##MeTOO

when i use http with custom header my AuthService works . but when i add HttpClient it does not work and gives same issue .

Question : Should i have to change response and headers from @angular/http to any other liberary or any other change required ?

2nd

is there no need of mapping to json ,observable etc .while interacting with server rest services ? wondering ....

Just adding a comment that this is still an issue with version 5.1.1

I've solved this issue on our application by splitting the AuthProvider in AuthProvider and AuthTokenProvider.

The only role of AuthTokenProvider is fetch (or not) the token from Storage.

The Interceptor uses the AuthTokenProvider, that doesn't use HTTP, so doesn't create a cyclic dependency.

The AuthProvider also uses AuthTokenProvider.

@Injectable()
export class AuthTokenProvider {
  private tokenSubject: ReplaySubject<AuthToken> = new ReplaySubject(1);
  private fetchTokenObservable: Observable<AuthToken>;

  readonly TOKEN_STORAGE_KEY = 'auth.token';

  constructor(
    public storage: StorageProvider,
  ) {
    this.fetchToken().subscribe(() => {});
  }

  /**
   * @returns {Observable<AuthToken>}
   */
  observeToken(): Observable<AuthToken> {
    return this.tokenSubject.asObservable();
  }

  /**
   * Return an observable that resolves when authProvider
   * is ready (token loaded from storage).
   * @returns {Observable<AuthToken>}
   */
  observeReady(): Observable<AuthToken> {
    return this.fetchTokenObservable;
  }

  /**
   * @param {AuthToken} token
   */
  public setToken(token: AuthToken): Observable<any> {
    return this.storage.set(this.TOKEN_STORAGE_KEY, token).map(() => {
      this.tokenSubject.next(token);

      return token;
    });
  }

  /**
   * Logout
   * @returns {Promise<any>}
   */
  public removeToken(): Observable<any> {
    return this.storage.remove(this.TOKEN_STORAGE_KEY).map(() => {
      this.tokenSubject.next(null);
    });
  }

  /**
   * Fetch token from storage if available
   */
  private fetchToken(): Observable<AuthToken> {
    // This avoids multiple unnecessary storage calls
    if (!this.fetchTokenObservable) {

      this.fetchTokenObservable = this.storage.get(this.TOKEN_STORAGE_KEY).map((token) => {
        this.tokenSubject.next(token);
        return token;
      });

      return this.fetchTokenObservable;
    }
  }
}

Hi, for me work like this

`export class SecurityFactory implements HttpInterceptor {
private authService: AuthService;
constructor(@Inject(forwardRef(() => Injector)) private injector: Injector) {
}
intercept(req: HttpRequest, next: HttpHandler): Observable> {
this.authService = this.injector.get(AuthService);
return next.handle(req).do(event => {
if (event['status'] === 401) {
this.authService.logout('expired');
}
});
}
}

export function tokenGetter() {
return sessionStorage.getItem('token');
}

@NgModule({
imports: [
JwtModule.forRoot({
config: {
tokenGetter: tokenGetter,
skipWhenExpired: true,
whitelistedDomains: ['localhost:4200']
}
})
],
providers: [
{provide: HTTP_INTERCEPTORS, useClass: SecurityFactory, multi: true}
]
})
export class AuthModule {
}`

For me it works like this.

this.injector.get(AuthService).logout();

Hey, maybe I miss something but I would not recommend to use injector.get because that approach introduces service location.
This makes interceptors harder to test.

For now I have one single service that uses @angular/http. This service is used by my interceptor.

Once this issue is solved I can easily switch to the new HttpClient.

I resolved this similar to @iget-master by refactoring AuthService into a helper service that is independent of HttpClient (AuthHelper: a class that is only concerned with getting/setting tokens in local storage as well as caching failed requests) and the original AuthService, which now only contains HttpClient-dependent functions.

The original dependency graph:
HttpInterceptors --> AuthService --> HttpClient --> HttpInterceptors

The fixed dependency graph:
HttpInterceptors --> AuthHelper <-- AuthService --> HttpClient -->HttpInterceptors

This not only fixes the cyclical dependency, but makes the code cleaner by better separating concerns. AuthService was concerned about both the implementation of locally storing tokens and the implementation of requesting/processing tokens from the application server. Now it only does the latter and delegates the former responsibility to the helper class.

This doesn't resolve the use-case of wanting to make an http request in the interceptor function (see @serhiisol 's comment), but that seems like a very dangerous use-case. What do you expect will happen when your interceptor intercepts its own request???

I solved the problem the same way @GregOnNet did, for now.

I had this error because my token refresh request was getting intercepted again. Http calls from inside interceptors will fire the interceptors again, which makes another http call, which fires the interceptors again.... until stack overflow.

@alxhub posted a good solution to preventing recursive calls to interceptors when you need to make an HTTP request from inside one

In your dependent service call, Inject HttpBackend and use that instead of HttpClient. Per The Docs

Interceptors sit between the HttpClient interface and the HttpBackend. When injected, HttpBackend dispatches requests directly to the backend, without going through the interceptor chain.

I don't see use case where this solution doesn't work, so I think this issue can be closed if the documentation on Http Interceptors is updated to point out usage of HttpBackend to bypass them in this situation.

Hi,

I am a little new to Angular so please bear with me :)

I got the circular reference issue after upgrading to Angular 5.2.0 and started following the advice on not injecting HttpClient via the constructor but rather using an Injector.
I have an Authentication Service that uses the msal for javascript (I use Azure B2C) and the authentication service used to inject a 'ConfigService' via the constructor to get the B2C policy info etc via an Http get.
I also have an interceptor that sets the header 'bearer' token upon return of the B2CCallback

My config service injects the HttpClient via the constructor and makes a call in a method to load the data from the back-end api.
I changed the ConfigService injection in the AuthenticationService to use the injector too and but I am running into 2 issues now.

1) The interceptor is not too happy with my Injector with error: TypeError: this.inj.get is not a function.

@Injectable()
export class AuthenticationHttpInterceptor implements HttpInterceptor {
    private authenticationService: AuthenticationService;

    constructor(private inj: Injector) {    }

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

        this.authenticationService = this.inj.get(AuthenticationService);
        return Observable.fromPromise(this.authenticationService.getAuthenticationToken())
            .switchMap(token => {
                req = req.clone({
                    setHeaders: {
                        Authorization: `Bearer ${token}`
                    }
                });
                return next.handle(req);
            });
    }
}

2) My config service is not being called anymore with the AuthenticationService returning all sorts of undefined types. Specifically here:

` constructor(private inj: Injector) {
this.configService = inj.get(ConfigService);

    this.b2CConfig = this.configService.getB2CConfig;
    this.authority = `https://login.microsoftonline.com/tfp/${this.b2CConfig.tenant}/${this.b2CConfig.signUpSignInPolicy}`;`

Any advice will be appreciated.

Thanks
Mike

For those of you thinking you can switch to the new HttpClient and it will fix it, I have switched for months now, but here's where you will face an issue:

I do recommend people to switch over to the new HttpClient... it will give you a lot of nice features. But I have an error handler across the app (ErrorHandler interface) and I got this same error when I tried to inject HttpClient into my ErrorHandler:

    constructor(http: HttpClient) {
    }

Since the ErrorHandler actually intercepts errors from Http as well, that's where the issue came in. So injector.get way was the only way to resolve it... and it's the only place in my large app where I am using the Injector. I want to remove it once I find a better way or there's a fix for this.

@Injectable()
export class ErrorHandlerService implements ErrorHandler {
    private http: HttpClient;

    constructor(private injector: Injector) {
        // Cyclic reference if I inject httpClient through constructor
        setTimeout(() => {
            this.http = this.injector.get(HttpClient);
        })
    }

    handleError(error: any) {
        console.log('Client Error:', error);
    }
}

I see this was closed with the release of 5.2.3 (and it is mentioned in the changelog).
But it still doesn't work.
I've just updated to 5.2.3 (nuked node_modules and package-lock.json first)
Then I've replaced the old code in my interceptor:

  constructor(
    private injector: Injector
  ) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authService = this.injector.get(AuthService);
    const token = authService.getToken();
    const reqCopy = req.clone({
      headers: req.headers.append('Authorization', `Bearer ${token}`)
    });
    return next.handle(reqCopy);
  }

with what it should be:

  constructor(
    private authService: AuthService
  ) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = this.authService.getToken();
    const reqCopy = req.clone({
      headers: req.headers.append('Authorization', `Bearer ${token}`)
    });
    return next.handle(reqCopy);
  }

And I get the cyclic dependency error when I try to build with the --prod flag.

@MrCroft I just updated to angular 5.2.3, it is working just fine.
yarn upgrade --scope @angular
Maybe there is something more causing the problem in your case.

I also get the cyclic dependency error when I try to build with the --prod flag.

I was getting the same cyclic error when using angular 5.2.2.
Ran npm update in Visual Studio Code and that updated to 5.2.4. No more error. Seems like the Angular team addressed the issue with the latest release.

@jzahoor I'd like to know more about the fix.
My work around (many months ago) was using "fetch" inside the interceptor in order to completely bypass angular...
Thanks

@melicerte sorry for the late response, didn't realize I had to pay my hosting provider. It was weird not getting email notifications for a few days, until I realized why :)

On topic: It turns out, if you have a Universal app, you still get the error. Of course I didn't notice this until I decided to create a new app, from scratch 😄
I don't even have to build the Universal app, it just needs to be there.
Simply running ng serve --prod or ng build --prod throws the error (this builds the first app in the apps array, which is the client in my case).

@alxhub I've made a repo here to reproduce it.
Simply running npm install and ng serve --prod (or ng build --prod to build the client app) will produce the error.
Weird thing is that it will mention app.server.module, even though you're just building the client app.
Not sure if this is an issue for the CLI rather than Angular?

Angular CLI: 1.6.8
Angular: 5.2.4
Node: 8.9.4
OS: win32 x64

@marcincichocki: interested to know how MY_REQUEST is being sent to your interceptor. I like your solution of just bypassing interceptors for certain things like auth, just not sure how to use your example for my existing interceptor.

@jdhines In that particular example you would need to provide MY_REQUEST token via dependency injection:

@NgModule({
  providers: [
    {
      provide: MY_REQUEST,
      useValue: new HttpRequest() // set your HttpRequest here, or use factory function if you need DI
    }
  ]
})

However this is good if you need highly portable and reusable code(let's say you have multiple apps that use different backends). I you don't, just set HttpRequest as property on interceptor itself:

@Injectable()
export class MyInterceptor  {
  myRequest = new HttpRequest()
}

@marcincichocki: thanks (and apologies for the thread here). Pardon what's probably a basic question; this is all pretty foreign to me, but is myRequest the backend stand-in for what is http.get('userInfo') in my auth-service? I assume you'd actually pass something to new HttpRequest()? If so, what?

@jdhines Yes, you must pass arguments describing your request. That class have few overloads

https://github.com/angular/angular/blob/c8a1a14b87e5907458e8e87021e47f9796cb3257/packages/common/http/src/request.ts#L129-L164

Most commonly you would use something like this:

const myRequest = new HttpRequest('POST', 'url/here', payload, options) // post
const myRequest2 = new HttpRequest('GET', 'url/here', options) // get

If you have this problem on Ionic, you might find yourself struggling with Maximum Call Stack Size Exceeded. Just do the inj.get(...) inside platform.ready().then(() => [...]); inside of the Interceptor!

I got stuck with a similar cyclic dependency and resorted to do the workaround of manually injecting the service https://stackoverflow.com/questions/49240232/getting-a-cyclic-dependency-error

I still have this problem, anyone has soved anyway?
I'm trying create a interceptor for include an Authorization in my Header.

The error: ERROR Error: Uncaught (in promise): RangeError: Maximum call stack size exceeded

My code:

```
intercept(request: HttpRequest, next: HttpHandler): Observable> {
let tokenProvider = this.injector.get(TokenProvider);

return Observable.create((observer: any) => {
  setTimeout(() => {
    observer.next(tokenProvider.getAsyncToken());
    observer.complete();
  });
}).mergeMap((token) => {
  if(token){
    request = request.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
  }
  return next.handle(request);
});

}

My TokenProvider:

async getAsyncToken(): Promise {
return new Promise((resolve) => {
let token = localStorage.getItem('token');
let time = Math.round(+new Date() / 1000);
if(!token){
this.http.post(${this.urlApi}/${this.urlAuth}, {timestamp: time}).subscribe(data => {
resolve(data.token);
}, err => {
console.log(err);
});
}else{
resolve(token);
}
});
}

@thiagodamico I now have the exact same error message as you and I don't think I have touched anything in my source code.

Me too.

I'm not sure why noone else has pointed this out but apparently this is fixed for Angular 6 by means of introducing an HttpInterceptingHandler which itself lazily loads the interceptors and avoids the circular dependency.

Any example for HttpInterceptingHandler usage?

@ronen1malka You are not supposed to use this class as it is not part of the public API. Simply upgrade to the 6.0.2 version and keep using the HttpInterceptor class.

@simeyla pointed out about HttpInterceptorHandler, good point!
But I wonder, why did the Angular team decided to create yet another concrete class to solve cyclic dependency problems instead of just adding an interface with the HttpClient signature

the bad part is something not working upgrade it !! lot of dirt being pushed on NPM

If you have this problem on Ionic, you might find yourself struggling with Maximum Call Stack Size Exceeded. Just do the inj.get(...) inside platform.ready().then(() => [...]); inside of the Interceptor!

Works with ionic 3 and angular 5

got same issue on angular 6 but solved with this method https://github.com/angular/angular/issues/18224#issuecomment-329939990

@Toxicable thank you, your solution works for me!

I got the same issue under angular 5.0.x, but it seems that the issue is fixed under angular 5.2.11. @Toxicable 's sulotion works under both 5.0.x and 5.2.11

This also works:

import { Injectable } from '@angular/core';
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { AuthService } from '../auth/auth.service';

@Injectable({
  providedIn: 'root'
})
export class JwtInterceptorService implements HttpInterceptor {

  constructor(private authService: AuthService) {
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Add authorization header with bearer token if available.
    const token: string = this.authService.tokenValue;
    if (token) {
      request = request.clone({
        setHeaders: {
          Authorization: `Bearer ${token}`
        }
      });
    }

    return next.handle(request).pipe(catchError((error: HttpErrorResponse) => {
      // Auto logout if 401 response returned from api
      if (error.status === 401) {
        this.authService.unAuthUser();
      }
      return throwError(error);
    }));
  }
}

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings