AngularJS - http interceptor - resend all request after token refresh
Your interceptor needs to keep track of whether or not it has an authentication request "in flight". It can do this by keeping a reference to the promise returned by the authentication request. If there is a request in flight and you get another 401, just use that cached promise instead of initiating a new request. Also, you should consider adding logic to account for the case when '/api/auth/refresh' itself returns a 401.
app.factory('AuthInterceptor', function($q, $injector, RESOURCE_URL, API_BASE, authService) {
var inflightAuthRequest = null;
return {
request: function(config) {
config.headers = config.headers || {};
if (authService.getAccessToken()) {
if (config.url.substring(0, RESOURCE_URL.length) !== RESOURCE_URL) {
config.headers.Authorization = 'Bearer ' + authService.getAccessToken();
}
}
return config;
},
responseError: function(response) {
switch (response.status) {
case 401:
var deferred = $q.defer();
if(!inflightAuthRequest) {
inflightAuthRequest = $injector.get("$http").post(API_BASE + '/api/auth/refresh', {refreshtoken: authService.getRefreshToken()});
}
inflightAuthRequest.then(function(r) {
inflightAuthRequest = null;
if (r.data.data.accesstoken && r.data.data.refreshtoken && r.data.data.expiresin) {
authService.setAccessToken(r.data.data.accesstoken);
authService.setRefreshToken(r.data.data.refreshtoken);
authService.setExpiresIn(r.data.data.expiresin);
$injector.get("$http")(response.config).then(function(resp) {
deferred.resolve(resp);
},function(resp) {
deferred.reject();
});
} else {
deferred.reject();
}
}, function(response) {
inflightAuthRequest = null;
deferred.reject();
authService.clear();
$injector.get("$state").go('guest.login');
return;
});
return deferred.promise;
break;
default:
authService.clear();
$injector.get("$state").go('guest.login');
break;
}
return response || $q.when(response);
}
};
});
The solution of Joe Enzminger is great. But I had a few issues with the callback as it didn't execute. Then I noticed a little typo in inflightAuthRequest/inFlightAuthRequest.
My complete solution is now:
(function() {
'use strict';
angular.module('app.lib.auth', []);
angular.module('app.lib.auth')
.factory('authService', authService);
angular.module('app.lib.auth')
.factory('AuthInterceptor', AuthInterceptor);
function authService($window) {
return {
getToken: function() {
return $window.localStorage.getItem('JWT');
},
getRefreshToken: function() {
return $window.localStorage.getItem('Refresh-JWT');
},
setRefreshToken: function(token) {
$window.localStorage.setItem('Refresh-JWT', token);
},
setToken: function(token) {
$window.localStorage.setItem('JWT', token);
},
clearAllToken: function(){
$window.localStorage.removeItem('JWT');
$window.localStorage.removeItem('Refresh-JWT');
},
clearToken: function(){
$window.localStorage.removeItem('JWT');
},
isLoggedIn: function() {
if ($window.localStorage.getItem('JWT') === null) {
return false;
}
else {
return true;
}
},
toLogin: function(){
$window.location.href = "http://" + $window.location.host + "/tprt/login";
}
}
}
function AuthInterceptor($q, $injector, authService) {
var inFlightAuthRequest = null;
return {
request : function(config) {
config.headers = config.headers || {};
if(authService.getToken()){
config.headers['Authorization'] = authService.getToken();
}
return config;
},
responseError : function(response) {
if(response.config.url == URLS.api_refresh_token){
console.log(JSON.stringify(response));
authService.clearAllToken();
authService.toLogin();
}else{
switch (response.status) {
case 401:
authService.clearToken();
var deferred = $q.defer();
if (!inFlightAuthRequest) {
inFlightAuthRequest = $injector.get("$http").post(
URLS.api_refresh_token, {
refreshtoken : authService.getRefreshToken()
});
}
inFlightAuthRequest.then(function(r) {
inFlightAuthRequest = null;
console.log(JSON.stringify(r));
authService.setToken(r.data.accesstoken);
$injector.get("$http")(response.config).then(function(resp) {
deferred.resolve(resp);
}, function(resp) {
deferred.reject(resp);
});
}, function(error) {
inFlightAuthRequest = null;
deferred.reject();
authService.clearAllToken();
authService.toLogin();
return;
});
return deferred.promise;
break;
default:
return $q.reject(response);
break;
}
return response || $q.when(response);
}
}
}
}
})();