How to handle multiple responses in Angular responseError interceptor
Asked Answered
P

2

8

I'm currently using the following code to rethrow a request that returns a 401 from my API:

responseError: function(rejection) {
                var authData = localStorageService.get('authorizationData');
                if (rejection.status === 401 && authData) {
                    var authService = $injector.get('authService');
                    var $http = $injector.get('$http');
                    ̶v̶a̶r̶ ̶d̶e̶f̶e̶r̶r̶e̶d̶ ̶=̶ ̶$̶q̶.̶d̶e̶f̶e̶r̶(̶)̶;̶ 

                    var promise = authService.refreshToken();

                    return  ̶d̶e̶f̶e̶r̶r̶e̶d̶.̶ promise.then(function () {
                        return $http(rejection.config);
                    });
                }
                return $q.reject(rejection);
            }

This works great for 1 request, but it doesn't seem to work if I get two 401s back from a single page, such as when the page is loading with two api calls to populate different sections. How can I get my interceptor to rethrow multiple deferred calls?

Also, shouldn't the interceptor fire for each 401 individually? Not ideal, it would cause multiple refresh calls on a single page, but an improvement from missing data due to a call not being rethrown.

Screenshot:

enter image description here

Pedroza answered 28/7, 2017 at 22:58 Comment(3)
shouldn't the interceptor fire for each 401 individually - it should. If this doesn't happen, please, provide stackoverflow.com/help/mcve . A plunk/fiddle that is able to replicate the problem may help.Spat
You are getting two 401s because you are firing off, in parallel, two XHRs with stale tokens. What problem is that causing? Does it eventually get valid data for both XHRs?Honorarium
@Honorarium The behavior I'm seeing is two async requests used to populate the page fire off, both 401, the 2nd(?) request gets rethrown and say I have a select per call, the 1st select is blank, the 2nd one has data.Pedroza
H
3

One approach is to save the token promise and chain the second and subsequent retries until the token refresh is complete:

responseError: function(rejection) {
    var authService = $injector.get('authService');
    var $http = $injector.get('$http');
    var tokenPromise = null;

    var authData = localStorageService.get('authorizationData');
    if (rejection.status === 401 && authData) {

        if (!tokenPromise) {
            tokenPromise = authService.refreshToken()
              .finally(function() {
                tokenPromise = null;
            });
        };  

        return tokenPromise.then(function () {
            return $http(rejection.config);
        });
    } else {
        throw rejection;
    }
}

In the above example the rejection handler create a token refresh promise and subsequently nulls it when the token refresh settles (either fulfilled or rejected). If another rejection occurs while the token refresh is in progress, the retry in chained (and delayed) until the token refresh XHR completes.

Honorarium answered 31/7, 2017 at 17:38 Comment(2)
That sounds exactly like what I need, however I posted an updated screen shot with the code in place, looks like it rethrows a single request like my old one did. Not sure how to fix it, but how does the above tokenPromise know about multiple requests?Pedroza
Great. Exactly what I needed 👏Nucleate
M
1

quite similar georgeawg answer...

responseError: function(rejection) {
                var authData = localStorageService.get('authorizationData');
                if (rejection.status === 401 && authData && !isAuthRequest() /* If request for refresh token fails itself do not go into loop, i.e. check by url */) {
                    var authService = $injector.get('authService');
                    var $http = $injector.get('$http');

                    var promise = authService.refreshTokenExt(); // look below

                    return  ̶promise.then(function () {
                        return $http(rejection.config);
                    });
                }
                return $q.reject(rejection);
            }

AuthService:

...
var refreshAuthPromise;

service.refreshTokenExt = function() {
  if (refreshAuthPromise == null) {
    refreshAuthPromise = authService.refreshToken().catch(function() {
      // Cant refresh - redirect to login, show error or whatever
    }).finally(function() {
      refreshAuthPromise = null;
    });
  }
  return refreshAuthPromise;
}
Merow answered 7/8, 2017 at 17:5 Comment(1)
I gave this a shot this morning, the network capture looks the same unfortunately. I'm going with this is impossible to do with two async requests with an interceptor in Angular 1.6. However, I can make my calls none async by chaining their promises on a per page level and then it works, so I guess I'm going with plan B.Pedroza

© 2022 - 2024 — McMap. All rights reserved.