3

Angular http interceptors not applied for requests initialised from within interceptors?

In our codebase, every request to our api has an url that starts with /api/ and we have an interceptor that updates those requests with the actual address of our api (making it easy to switch between an development and production environment for one thing). After updating the url, the interceptor also adds an Authorization header if an access token is present. This all works perfectly.

However, sometimes the access token is expired, but we still have an refresh token and before continuing with the actual request, I first want to make an request to our api to obtain a new access token.

So, in the request part of our interceptor, we call a service responsible for refreshing the access token and in a then callback returned by that method, we update the config of the original request:

//This interceptor only applies to our api calls, do nothing for other requests
if(config.url.substring(0, API_PREFIX.length) !== API_PREFIX) {
    return config;
}

config.url = config.url.replace(API_PREFIX, API_ENDPOINT);

// if we are authenticated, we add the authorization header already
if(AuthSessionService.isAuthenticated()) {
    config.headers['Authorization'] = "Bearer " + AuthSessionService.getAccessToken();
    return config;
}

// so we are not authenticated, but we still do have a refresh-token, this means, we should get a new access-token
if(AuthSessionService.hasRefreshToken()) {
    var deferred = $q.defer(),
        loginService = angular.injector(['MyApp']).get('LoginService');

    loginService.refresh().then(function(result) {
        config.headers['Authorization'] = "Bearer " + AuthSessionService.getAccessToken();
        deferred.resolve(config);
    }).catch(function() {
        deferred.reject('Failed to refresh access token');
    });

    return deferred.promise;
}

//no access-token, no refresh-token, make the call without authorization headers
return config;

However, the requests made by the login-service do not seem to have the interceptor applied, so the request goes to /api/login instead of the actual api endpoint.

Is this by Angular's design, that no interceptor is applied when a new http request is being made from within an interceptor's request method?

5
  • How are you injecting loginService? I'm guessing it has a dependency to $http, right? So that would be a circular dependency issue. I'm guessing the workaround is the cause of your issue. Commented Aug 11, 2015 at 11:44
  • Also using the anti-pattern of creating your own promise rather than using the promise returned by $http Commented Aug 11, 2015 at 11:45
  • i personally would make use of a broadcast then you can start your new call from wherever you want Commented Aug 11, 2015 at 12:00
  • @SergiuParaschiv the loginService is retrieved through `angular.injector().get('LoginService') to prevent a circular dependency issue. Commented Aug 11, 2015 at 12:10
  • @charlietfl I was returning the promise of the LoginService (which is the promise returned by $http) before, but without result luck. So I tried it this way, but no luck either. Commented Aug 11, 2015 at 12:13

1 Answer 1

1

Your code flow is this:

0) AngularJS defines $httpProvider;

1) You define loginService which depends on $httpProvider to inject $http;

2) You define a HTTP interceptor that depends on loginService and changes the way $http works;

3) You define other services which inject $http.

Take a look at this function, the $get method any AngularJS provider must supply. It is called each time your services inject $http as a dependency and returns $http.

Now if you go back to line 396 you'll see that a list of reversedInterceptors is built when $get is called. It's a local variable so the $http instance returned will be able to use it, and this is the answer right here, reversedInterceptors is a different reference for each "instance" of $http you inject.

So the $http injected in loginService (1) is different from the ones injected in all your other services (3) and the difference is that it's reversedInterceptors does not yet contain the interceptor you added in step (2).

Regarding service vs provider. A service is built upon a provider and basically does this:

function MyService() {
    ...
}

angular.service('MyService', MyService);

// which Angular translates to this


function MyServiceProvider() {
    var myService = new MyService();

    this.$get = function() {
        return myService;
    };
}

angular.provider('MyServiceProvider', MyServiceProvider);

Whilst a provider is this:

function MyOtherProvider() {
    var someStuff = [1, 2, 3];

    this.$get = function() {
        var otherStuff = [5, 6, 7]; // this is how reversedInterceptors is 
                                    // initialized in `$httpProvider`

        function get(url) {
            // do stuff, maybe use otherStuff
        }

        return {
            get: get
        };
    };
}

angular.provider('MyOtherProvider', MyOtherProvider);

Angular only instantiates MyServiceProvider and MyOtherProvider once, but what happens inside is different.

Sign up to request clarification or add additional context in comments.

4 Comments

Wow, thanks. But then I think my understanding of these services is incomplete. I thought having a service being injected anywhere, would always inject the same instance..
But it's not a service :) A provider is not a service. It's something Angular instantiates that has a $get method Angular then calls each time you inject it and gives you the return value. As you can see the list of interceptors is being built each time $get is called, so it's dependent on the time when it was called. An Angular service on the other hand always returns the same instance.
Thanks. I changed the interceptor to have the $injector service injected and using that service to get the loginService returns that service with the interceptor configured!
Yup, that's why I initially said "I'm guessing the workaround is the cause of your issue." :) Injecting $injector means you can get an "up to date" version of $http each time you need it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.