auth - angularjs http post example with parameters
AngularJS-interceptor http-reenvía todas las solicitudes después de actualizar el token (2)
Tengo una aplicación angular que a veces realiza múltiples solicitudes de $ http.get por estado. La aplicación utiliza JWT para la autenticación del usuario con tokens de actualización. El servidor API envía 401
en cada solicitud que falló debido a un error de autenticación. Hice un http interceptor
que solicita un token nuevo con el token de actualización en 401 errores y luego reenvía la solicitud original.
El problema es que si un estado realiza, por ejemplo, 2 $ solicitudes http.get y ambos obtienen una respuesta 401, entonces renuevo el token de acceso dos veces. Obviamente, solo quiero actualizar el token una vez, PERO todavía quiero reenviar AMBAS solicitudes fallidas.
¿Es esto posible y si es así, cómo?
app.factory(''AuthInterceptor'', function($q, $injector, RESOURCE_URL, API_BASE, authService) {
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();
$injector.get("$http").post(API_BASE + ''/api/auth/refresh'', {refreshtoken: authService.getRefreshToken()}).then(function(r) {
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) {
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);
}
};
});
La solución de Joe Enzminger es genial. Pero tuve algunos problemas con la devolución de llamada ya que no se ejecutó. Entonces noté un pequeño error tipográfico en inflightAuthRequest / inFlightAuthRequest.
Mi solución completa es ahora:
(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);
}
}
}
}
})();
Su interceptor debe realizar un seguimiento de si tiene o no una solicitud de autenticación "en vuelo". Puede hacerlo manteniendo una referencia a la promesa devuelta por la solicitud de autenticación. Si hay una solicitud en vuelo y obtiene otro 401, simplemente use esa promesa en caché en lugar de iniciar una nueva solicitud.
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);
}
};
});