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

RandomUs1r
  • 4,010
  • 1
  • 24
  • 44
  • 2
    *shouldn't the interceptor fire for each 401 individually* - it should. If this doesn't happen, please, provide http://stackoverflow.com/help/mcve . A plunk/fiddle that is able to replicate the problem may help. – Estus Flask Jul 29 '17 at 01:03
  • 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? – georgeawg Jul 29 '17 at 01:21
  • @georgeawg 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. – RandomUs1r Jul 31 '17 at 16:14

2 Answers2

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.

georgeawg
  • 48,608
  • 13
  • 72
  • 95
  • 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? – RandomUs1r Jul 31 '17 at 19:36
  • Great. Exactly what I needed – artsnr Aug 18 '21 at 12:31
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;
}
Petr Averyanov
  • 9,327
  • 3
  • 20
  • 38
  • 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. – RandomUs1r Aug 09 '17 at 17:10