3
votes

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?

1
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. - Sergiu Paraschiv
Also using the anti-pattern of creating your own promise rather than using the promise returned by $http - charlietfl
i personally would make use of a broadcast then you can start your new call from wherever you want - stackg91
@SergiuParaschiv the loginService is retrieved through `angular.injector().get('LoginService') to prevent a circular dependency issue. - schmkr
@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. - schmkr

1 Answers

1
votes

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.