CORS - Unable to logout from application - javascript

I am doing a proof of concept on AngularJS and SpringMVC 5.x, Tomcat and single-sign-on (SSO) to login and logout from the application. When session is timed-out or Invalid session, the application is not redirected to login page. I am getting CORS error message.
Here is the sample JavaScript code for Invalid Session and Session timeout, please help how to apply CORS on the below code.
//Invalid Session
TestApp.factory('respInterceptor', function($location, $window, $cookies) {
return {
response: function(resp) {
//console.log(" Test : " + JSON.stringify(resp));
return resp;
},
responseError : function(error) {
console.log(" Error >>> : "+error.status )
if(error.status == 401 || error.status == 302) {
$cookies.JSESSIOINID = '';
console.log("invalid session");
window.location = "http://login.boshiftdev.com/app/ui/login.jsp";
}
}
};
})
//Session Timeout
$scope.logout = function() { $cookies.JSESSIOINID = '';
console.log("logout");
$http({
method: 'POST',
url: contextURL+'/logout',
headers: {'Content-Type': 'application/json'},
params: {data: $rootScope.userSession }
}).
success (function(data) {
$rootScope.userSession = null; $window.location = 'login.jsp';
window.location = ""
}).
error (function(data) {
$rootScope.userSession = null; $window.location= 'login.jsp';
console.error('');
});
}

Related

passing error message from service to controller in angularjs

Controller.js
var vm = this;
vm.admin = {};
vm.add = function () {
API.addAdmin(token, vm.admin)
.then(function (resp) {
vm.hideForm = true;
vm.showButton = true;
Notify.green(resp);
}, function (resp) {
Notify.red(resp);
});
};
API.js
function addAdmin(token, dataObj) {
return Constant.getApiUrl()
.then(function (url) {
$http({
method: 'POST',
url: url + '/client/admin',
headers: {
'Token': token
},
data: dataObj
}).then(handleResp);
function handleResp(resp) {
var responseStatus = (resp.status >= 200 && resp.status < 300) ? 'good' : 'bad';
if (responseStatus === 'good') {
console.log("Success" + resp);
return resp;
} else {
console.log("Failed" + resp);
return resp;
}
}
})
}
If I get a success response in API then i need to connect it to success function in my controller and if i get error message in my API, then i need it to connect it to error function in my controller.How should I evaluate the response status from my API(is either success or error).
I don't want to pass successfn, errorfn from my controller to API(only if there's no alternative).
I need to get the response data from API to controller to show it in Notify message.
Thank You!
In service (assign response values in "originalData"):
angular.module('appname').service('myserviceName', function(yourExistingService){
this.myFunction= function(originalData) {
//for next line to work return promise from your addAdmin method.
var promise = yourExistingService.getResponseFromURL(originalData);
return promise;
}
});
And in your controller :
var promise = myserviceName.myFunction($scope.originalData);
promise.$promise.then(function() {
console.log($scope.originalData);
});
And then you can check you "originalData" and write code according to your need.For more detail you can have a look on this http://andyshora.com/promises-angularjs-explained-as-cartoon.html.

unsupported_grant_type error in angularJS and web API

I am trying to achieve user login
and logout using angularJS and web Api
But the server always return badrequest (400)
exception
the error is coming from this bit of code
AuthApp.factory('authService', ['$http', '$q', 'localStorageService', function ($http, $q, localStorageService) {
var authServiceFactory = {};
var _authentication =
{
isAuth: false,
userName: ""
};
// this is the login function
authServiceFactory.login = function (loginData)
{
var data = "grant_type=password&username=" + loginData.userName + "&password=" + loginData.password; //is not working
//var data = { username: loginData.userName, password: loginData.password, grant_type: "password" }; // I try this and is not working too
//data = $.param(data);
// how should I format my data for the web API to understand
var deferred = $q.defer();
// alert(data);
$http.post('/token', data, {
header: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).success(function (response) {
localStorageService.set('authorizationData', { token: response.access_token, userName: response.userName });
_authentication.isAuth = true;
_authentication.userName = loginData.userName;
deferred.resolve(response);
}).error(function (err) {
// _logout();
deferred.reject(err);
});
return deferred.promise;
}
authServiceFactory.logout = function ()
{
localStorageService.remove("authenticationData");
_authentication.isAuth = false;
_authentication.userName = "";
}
return authServiceFactory;
}]);
using postman to further see the error
this appears
{ "error": "unsupported_grant_type" }
I made google search but still no solution; how can I resolve this issue?
thanks in advance!!

Angular, Response to Preflight Request

I have an interceptor that handles all my requests on my controllers. I have a back-end web API that implements a refresh token but when I try to refresh my token and continue with the request being made I get "Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access. The response had HTTP status code 400." And the token request gives me {"error":"invalid_clientId","error_description":"ClientId should be sent."}
Interceptor:
var appInterceptors = angular.module("auth-Interceptor", ["ngRoute", "angular-loading-bar"]);
/* ==== Bearer token headers configuration ==== */
appInterceptors.factory("authentication-interceptor", ["$q", "$injector", "$rootScope", "$location", "cfpLoadingBar", function ($q, $injector, $rootScope, $location, cfpLoadingBar) {
var inFlightAuthRequest = null;
var deferred = $q.defer();
return {
// On request success
request: function (config) {
config.headers = config.headers || {};
if ($rootScope.globals.accessToken != null) {
config.headers.Authorization = 'Bearer ' + $rootScope.globals.accessToken;
}
// Return the config or wrap it in a promise if blank.
return config || $q.when(config);
},
requestError: function (rejection) {
debugger; //return debugger for more info
return rejection;
},
responseError: function (rejection) {
debugger;
if (rejection.status === 401) {
var refreshToken = window.localStorage.getItem("refreshToken"); //log the user in if there is an refresh token
$injector.get("$http").post(
$rootScope.globals.apiPath + "/accessControl/token",
{ client_id: "id", grant_type: "refresh_token", refresh_token: refreshToken },
{ 'Content-Type': 'application/x-www-form-urlencoded' }
)
.then(function (data) {
inflightAuthRequest = null;
if (data.access_token != undefined && data.refresh_token != undefined) {
window.localStorage.setItem("refreshToken", data.refresh_token);
window.localStorage.setItem("accessToken", data.access_token);
window.localStorage.setItem("rememberMe", true);
window.localStorage.setItem("time_expires_in", data.expires_in);
$injector.get("$http")(rejection.config).then(function (resp) {
deferred.resolve(resp);
}, function (resp) {
deferred.reject();
});
} else {
deferred.reject();
}
return $q.reject(rejection);
}, function (response) {
deferred.reject();
authService.clear();
$injector.get("$state").go('/login');
return;
});
return deferred.promise;
}
}
};
}]);
appInterceptors.config(["$httpProvider", function ($httpProvider) {
$httpProvider.interceptors.push("authentication-interceptor");
}]);
Service:
jobManagerApp.factory("authenticationService", ["$rootScope", "$http", "$location", function ($rootScope, $http, $location) {
return {
Login: function (username, password) {
return $http({
url: $rootScope.globals.apiPath + "/accessControl/token",
method: 'POST',
data: "userName=" + encodeURIComponent(username) +
"&password=" + encodeURIComponent(password) +
"&Scope=" + "website" +
"&grant_type=password" +
"&client_id=id",
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
},
RefreshToken: function (refreshtoken) {
return $http({
url: $rootScope.globals.apiPath + "/accessControl/token",
method: 'POST',
data: "client_id=" +
"&grant_type=refresh_token" +
"&refresh_token=" + refreshtoken,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
},
LogOut: function () {
return $http({
url: $rootScope.globals.apiPath + "/accessControl/logout",
method: "POST"
});
},
DoLogin: function (data, rememberMe) {
//save the tokens
if (rememberMe == true) {
window.localStorage.setItem("refreshToken", data.refresh_token);
window.localStorage.setItem("accessToken", data.access_token);
window.localStorage.setItem("rememberMe", true);
} else {
window.localStorage.removeItem("refreshToken");
window.localStorage.removeItem("accessToken");
}
//set the global values for user
$rootScope.globals.accessToken = data.access_token;
$rootScope.globals.refreshToken = data.refresh_token;
//hide the menu items for which the users does not have access rights
$rootScope.HideMenuItems();
//navigate to the page where the user originally wanted to go (returnLocation) or to the default page
var gotoLocation = $rootScope.globals.returnToLocation;
if (gotoLocation != "") {
$location.path(gotoLocation);
$rootScope.globals.returnToLocation = "";
} else {
//go to default page
$location.path("/home");
}
//set the logged in value only after the navigation has taken place as it is linked to the ng-show/hide of the toolbar and menu
$rootScope.globals.isLoggedIn = true;
}
};
}]);
Login:
jobManagerApp.controller("loginController", ["$scope", "$rootScope", "$location", "authenticationService", function ($scope, $rootScope, $location, authenticationService) {
$scope.LoginButtonDisabled = false;
$scope.LogonName = null;
$scope.Password = null;
$scope.Error = "";
$scope.Version = ($rootScope.globals.version.indexOf("-") == -1) ? $rootScope.globals.version : $rootScope.globals.version.substring(0, $rootScope.globals.version.indexOf("-"));
$scope.Login = function () {
$scope.LoginButtonDisabled = true;
if ($scope.LogonName !== null && $scope.Password !== null) {
//attempt login
authenticationService.Login($scope.LogonName, $scope.Password)
.success(function (data) {
$scope.LoginButtonDisabled = false;
if (data.access_token != undefined) {
//Time Expires
window.localStorage.setItem("time_expires_in", data.expires_in);
//Time user logged in
window.localStorage.setItem("time_logged_in", new Date().getTime());
//do the actual login
authenticationService.DoLogin(data, $scope.RememberMe);
}
else if (data.error_description != undefined) {
$scope.Error = data.error_description;
}
else {
$scope.Error = "Unexpected error occurred!";
}
})
.error(function (data, status, headers, config) {
$rootScope.globals.accessToken = null;
window.localStorage.removeItem("accessToken");
window.localStorage.removeItem("refreshToken");
$scope.LoginButtonDisabled = false;
});
} else {
$scope.Error = "Enter a username and password!";
$scope.LoginButtonDisabled = false;
}
};
var accessToken = window.localStorage.getItem("accessToken"); //log the user in if there is an access token
var refreshToken = window.localStorage.getItem("refreshToken"); //log the user in if there is an refresh token
var time_expires = window.localStorage.getItem("time_expires_in"); //Time token expires
var time_logged_in = window.localStorage.getItem("time_logged_in"); //Time user logged in
var time = new Date().getTime(); //CurrentTime
var tokenExpired; //variable to be used to setExpired
if (((time / 1000) - (time_logged_in / 1000)) >= time_expires) {
tokenExpired = true;
} else {
tokenExpired = false;
}
//Log test
console.log("Time left: " + (time_expires - ((time / 1000) - (time_logged_in / 1000))));
console.log(refreshToken);
//login
if (accessToken != null && tokenExpired == false && refreshToken != null) {
$rootScope.globals.accessToken = accessToken; //set this for the auth-interceptor to do its work
$rootScope.globals.showLoading = true;
$rootScope.globals.showLoading = false;
var data = {
access_token: accessToken,
expires_in: time_expires,
refresh_token: refreshToken
};
authenticationService.DoLogin(data, true);
//authenticationService.GetAuthenticationProperties().success(function (data) {
// $rootScope.globals.showLoading = false;
// data.access_token = accessToken;
// authenticationService.DoLogin(data, true);
//}).error(function () {
// $rootScope.globals.showLoading = false;
//});
} else if (refreshToken != null) {
//request a new access token
authenticationService.RefreshToken(refreshToken)
.success(function (data) {
if (data.access_token != undefined && data.refresh_token != undefined) {
$rootScope.globals.accessToken = data.access_token; //set this for the auth-interceptor to do its work
$rootScope.globals.refreshToken = data.refresh_token //Set the new refresh token
$rootScope.globals.showLoading = true;
$rootScope.globals.showLoading = false;
var data = {
access_token: data.access_token,
refresh_token: data.refresh_token,
expires_in: data.expires_in
};
//Renew the time logged in and the time time_expires
//Time Expires
window.localStorage.setItem("time_expires_in", data.expires_in);
//Time user logged in
window.localStorage.setItem("time_logged_in", new Date().getTime());
//Set the access token
tokenExpired = false //renew to false;
authenticationService.DoLogin(data, true);
}
})
.error(function (data, status, headers, config) {
$rootScope.globals.accessToken = null;
window.localStorage.removeItem("accessToken");
window.localStorage.removeItem("refreshToken");
$scope.LoginButtonDisabled = false;
});
}
}]);
Any help would much be appreciated.
I have managed to fix my own problem, on the interceptor I injected the authService functions and reset the localstorage access, I then added and "ALLOW OPTION" for option requests to be resolved on my web API:
Interceptor
appInterceptors.factory("authentication-interceptor", ["$q", "$injector", "$rootScope", "$location", "cfpLoadingBar", function ($q, $injector, $rootScope, $location, cfpLoadingBar) {
return {
// On request success
request: function (config) {
config.headers = config.headers || {};
if ($rootScope.globals.accessToken != null) {
config.headers.Authorization = 'Bearer ' + $rootScope.globals.accessToken;
}
// Return the config or wrap it in a promise if blank.
return config || $q.when(config);
},
requestError: function (rejection) {
debugger;
return rejection;
},
responseError: function (response) {
// error - was it 401 or something else?
if (response.status === 401) {
var deferred = $q.defer(); // defer until we can re-request a new token
var accessToken = window.localStorage.getItem("accessToken");
var refreshtoken = window.localStorage.getItem("refreshToken");
// Get a new token... (cannot inject $http directly as will cause a circular ref)
$injector.get("authenticationService").RefreshToken(refreshtoken).then(function (loginResponse) {
if (loginResponse) {
console.log(loginResponse);
$rootScope.globals.accessToken = loginResponse.data.access_token; // we have a new acces token - set at $rootScope
$rootScope.globals.refreshToken = loginResponse.data.refresh_token; // we have a new refresh token - set at $rootScope
//Update the headers
window.localStorage.setItem("accessToken", loginResponse.data.access_token);
window.localStorage.setItem("refreshToken", loginResponse.data.refresh_token);
window.localStorage.setItem("rememberMe", true);
//Time Expires
window.localStorage.setItem("time_expires_in", loginResponse.data.expires_in);
//Time user logged in
window.localStorage.setItem("time_logged_in", new Date().getTime());
// now let's retry the original request - transformRequest in .run() below will add the new OAuth token
$injector.get("authenticationService").ResolveDeferred(response.config).then(function (defResp) {
// we have a successful response - resolve it using deferred
deferred.resolve(defResp);
}, function (defResp) {
deferred.reject(); // something went wrong
});
} else {
deferred.reject(); // login.json didn't give us data
}
}, function (response) {
deferred.reject(); // token retry failed, redirect so user can login again
$location.path('/login');
return;
});
return deferred.promise; // return the deferred promise
}
return $q.reject(response); // not a recoverable error
}
};
}]);
AuthenticationService
RefreshToken: function (refreshtoken) {
return $http({
url: $rootScope.globals.apiPath + "/accessControl/token",
method: 'POST',
datatype: 'jsonp',
data: "client_id=id" +
"&grant_type=refresh_token" +
"&refresh_token=" + refreshtoken,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
},
LogOut: function () {
return $http({
url: $rootScope.apiPath + "/acess/logout",
method: "POST"
});
},
ResolveDeferred: function (config) {
return $http(config);
},
API
public override Task MatchEndpoint(OAuthMatchEndpointContext context)
{
if (context.IsTokenEndpoint && context.Request.Method == "OPTIONS")
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "authorization" });
context.RequestCompleted();
return Task.FromResult(0);
}
return base.MatchEndpoint(context);
}

$window.sessionStorage for login and logout ( token based);

I create a login and logout function with Node.js and Angular.js which is token based. The token I am saving into window storage.
The problem is if I logout it just logout for one browser and also if I loggedin it not recognize if I am already loggedin. I think I have to extend my programm.
My question is how can I delete the storage for every open browser where I loggedin? Or schould I ask within my code if I am loggedin and how could I do this?
Thanks in advance!
NODE.JS CODE
app.post('/logout', function(req, res){
jwt.verify(req.body.token, 'secretKey', function(err, decoded) {
console.log("Decoded " + decoded);
if(decoded._id != null){
User.findOne({
_id : decoded._id
}, function(err, user) {
if (err) {
console.log('Error occured', err);
} else {
if (user) {
res.end();
}
}
});
}else{
Console.log("Could not logout");
}
});
});
app.post('/login', function(req, res) {
User.findOne({
email : req.body.email
}, function(err, user) {
if (err) {
console.log('Error occured', err);
} else {
if (user) {
// check if password matches
if (req.body.password != undefined) {
var hashPWCheck = bcrypt.compareSync(req.body.password, user.password);
// true
//console.log(hashPWCheck);
if (!(hashPWCheck)) {
res.json({
success : false,
message : 'Authentication failed. Wrong password.'
});
console.log('Authentication failed. Wrong password.');
} else {
var token = jwt.sign(user, 'secretKey', {
expiresInMinutes : 60 // expires in 1 Minute
});
res.json({token : token, email : user.email});
console.log("Token created & sent to Client(UserCtrlLogin): " + token);
}
} else {
console.log("Password is required!");
}
} else {
console.log("Incorect E-Mail");
}
}
});
});
ANGULAR.js Code
app.controller('UserCtrlLogin', function($scope, $http, $window, $location, $rootScope) {
$scope.logout = function(){
var sessionlogout = $window.sessionStorage.getItem('token');
var formData = {
token : sessionlogout
};
$http.post('/logout', formData).success(function(data, status, headers, config) {
if(status == 200){
$rootScope.isAlive = false;
$rootScope.ali = false;
$window.sessionStorage.removeItem('token');
}else{
$window.sessionStorage.removeItem('token');
$rootScope.isAlive = false;
}
});
};
$scope.signin = function() {
var formData = {
email : $scope.email,
password : $scope.password
};
// $window.sessionStorage.removeItem('token');
$http.post('/login', formData).success(function(data, status, headers, config) {
console.log('Data: ' + data.email);
//console.log('Status: ' + status);
if (status == 200) {
if(data.email == "goekguel.ali#gmail.com"){
$rootScope.ali = true;
}
$rootScope.isAlive = true;
$window.sessionStorage.setItem('token', data.token);
console.log("Token saved into Storage from Server(Node.js function /login)");
}
}).error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
$window.sessionStorage.removeItem('token');
});
};
});
You need to save tokens in the database, and if you log in or log out in one browser you have to mark token as valid/invalid, and in another browser it's required to check token status on backend.
P.s. See satellizer, it's just my recommendation for front-end auth module.

Intercept Unathorized API calls with Angular

I am trying to intercept the 401 and 403 errors to refresh the user token, but I can't get it working well. All I have achieved is this interceptor:
app.config(function ($httpProvider) {
$httpProvider.interceptors.push(function ($q, $injector) {
return {
// On request success
request: function (config) {
var deferred = $q.defer();
if ((config.url.indexOf('API URL') !== -1)) {
// If any API resource call, get the token firstly
$injector.get('AuthenticationFactory').getToken().then(function (token) {
config.headers.Authorization = token;
deferred.resolve(config);
});
} else {
deferred.resolve(config);
}
return deferred.promise;
},
response: function (response) {
// Return the promise response.
return response || $q.when(response);
},
responseError: function (response) {
// Access token invalid or expired
if (response.status == 403 || response.status == 401) {
var $http = $injector.get('$http');
var deferred = $q.defer();
// Refresh token!
$injector.get('AuthenticationFactory').getToken().then(function (token) {
response.config.headers.Authorization = token;
$http(response.config).then(deferred.resolve, deferred.reject);
});
return deferred.promise;
}
return $q.reject(response);
}
}
});
});
The issue is that the responseError does an infinite loop of 'refreshes' because by Authorization header with the updated token, that is not being received by $http(response.config) call.
1.- App has an invalid token stored.
2.- App needs to do an API call
2.1 Interceptor catch the `request`.
2.2 Get the (invalid) stored token and set the Authorization header.
2.3 Interceptor does the API call with the (invalid) token setted.
3.- API respond that used token is invalid or expired (403 or 401 statuses)
3.1 Interceptor catch the `responseError`
3.2 Refresh the expired token, get a new VALID token and set it in the Authorization header.
3.3 Retry the point (2) with the valid refreshed token `$http(response.config)`
The loop is happening in point (3.3) because the Authorization header NEVER has the new refreshed valid token, it has the expired token instead. I don't know why because it supposed to be setted in the responseError
AuthenticationFactory
app.factory('AuthenticationFactory', function($rootScope, $q, $http, $location, $log, URI, SessionService) {
var deferred = $q.defer();
var cacheSession = function(tokens) {
SessionService.clear();
// Then, we set the tokens
$log.debug('Setting tokens...');
SessionService.set('authenticated', true);
SessionService.set('access_token', tokens.access_token);
SessionService.set('token_type', tokens.token_type);
SessionService.set('expires', tokens.expires);
SessionService.set('expires_in', tokens.expires_in);
SessionService.set('refresh_token', tokens.refresh_token);
SessionService.set('user_id', tokens.user_id);
return true;
};
var uncacheSession = function() {
$log.debug('Logging out. Clearing all');
SessionService.clear();
};
return {
login: function(credentials) {
var login = $http.post(URI+'/login', credentials).then(function(response) {
cacheSession(response.data);
}, function(response) {
return response;
});
return login;
},
logout: function() {
uncacheSession();
},
isLoggedIn: function() {
if(SessionService.get('authenticated')) {
return true;
}
else {
return false;
}
},
isExpired: function() {
var unix = Math.round(+new Date()/1000);
if (unix < SessionService.get('expires')) {
// not expired
return false;
}
// If not authenticated or expired
return true;
},
refreshToken: function() {
var request_params = {
grant_type: "refresh_token",
refresh_token: SessionService.get('refresh_token')
};
return $http({
method: 'POST',
url: URI+'/refresh',
data: request_params
});
},
getToken: function() {
if( ! this.isExpired()) {
deferred.resolve(SessionService.get('access_token'));
} else {
this.refreshToken().then(function(response) {
$log.debug('Token refreshed!');
if(angular.isUndefined(response.data) || angular.isUndefined(response.data.access_token))
{
$log.debug('Error while trying to refresh token!');
uncacheSession();
}
else {
SessionService.set('access_token', response.data.access_token);
SessionService.set('token_type', response.data.token_type);
SessionService.set('expires', tokens.expires);
SessionService.set('expires_in', response.data.expires_in);
deferred.resolve(response.data.access_token);
}
}, function() {
// Error
$log.debug('Error while trying to refresh token!');
uncacheSession();
});
}
return deferred.promise;
}
};
});
PLUNKER
I made a plunker & backend to try to reproduce this issue.
http://plnkr.co/edit/jaJBEohqIJayk4yVP2iN?p=preview
Your interceptor needs to keep track of whether or not it has a request for a new authentication token "in flight". If so, you need to wait on the result of the in-flight request rather than initiating a new one. You can do this by caching the promise returned by your AuthRequest and using the cached promise instead of creating a new one for every API requests.
Here is an answer to a similar question that demonstrates this.
For you example - here is an example implementation:
app.config(function ($httpProvider) {
$httpProvider.interceptors.push(function ($q, $injector) {
var inFlightRequest = null;
return {
// On request success
request: function (config) {
var deferred = $q.defer();
if ((config.url.indexOf('API URL') !== -1)) {
// If any API resource call, get the token firstly
$injector.get('AuthenticationFactory').getToken().then(function (token) {
config.headers.Authorization = token;
deferred.resolve(config);
});
} else {
deferred.resolve(config);
}
return deferred.promise;
},
response: function (response) {
// Return the promise response.
return response || $q.when(response);
},
responseError: function (response) {
// Access token invalid or expired
if (response.status == 403 || response.status == 401) {
var $http = $injector.get('$http');
var deferred = $q.defer();
// Refresh token!
if(!inFlightRequest){
inFlightRequest = $injector.get('AuthenticationFactory').refreshToken();
}
//all requests will wait on the same auth request now:
inFlightRequest.then(function (token) {
//clear the inFlightRequest so that new errors will generate a new AuthRequest.
inFlightRequest = null;
response.config.headers.Authorization = token;
$http(response.config).then(deferred.resolve, deferred.reject);
}, function(err){
//error handling omitted for brevity
});
return deferred.promise;
}
return $q.reject(response);
}
}
});
});
UPDATE:
It's not clear to me from your plunk exactly what the problem is, but there is a problem with your AuthenticationService. Recommended changes are below and here is a Plunkr that is a bit more complete (and includes tracking inflight requests):
app.factory('AuthenticationFactory', function($rootScope, $q, $http, $location, $log, URI, SessionService) {
//this deferred declaration should be moved. As it is, it's created once and re-resolved many times, which isn't how promises work. Subsequent calls to resolve essentially are noops.
//var deferred = $q.defer();
var cacheSession = function(tokens) {
SessionService.clear();
// Then, we set the tokens
$log.debug('Setting tokens...');
SessionService.set('authenticated', true);
SessionService.set('access_token', tokens.access_token);
SessionService.set('token_type', tokens.token_type);
SessionService.set('expires', tokens.expires);
SessionService.set('expires_in', tokens.expires_in);
SessionService.set('refresh_token', tokens.refresh_token);
SessionService.set('user_id', tokens.user_id);
return true;
};
var uncacheSession = function() {
$log.debug('Logging out. Clearing all');
SessionService.clear();
};
return {
login: function(credentials) {
var login = $http.post(URI+'/login', credentials).then(function(response) {
cacheSession(response.data);
}, function(response) {
return response;
});
return login;
},
logout: function() {
uncacheSession();
},
isLoggedIn: function() {
if(SessionService.get('authenticated')) {
return true;
}
else {
return false;
}
},
isExpired: function() {
var unix = Math.round(+new Date()/1000);
if (unix < SessionService.get('expires')) {
// not expired
return false;
}
// If not authenticated or expired
return true;
},
refreshToken: function() {
var request_params = {
grant_type: "refresh_token",
refresh_token: SessionService.get('refresh_token')
};
return $http({
method: 'POST',
url: URI+'/refresh',
data: request_params
});
},
getToken: function() {
//It should be moved here - a new defer should be created for each invocation of getToken();
var deferred = $q.defer();
if( ! this.isExpired()) {
deferred.resolve(SessionService.get('access_token'));
} else {
this.refreshToken().then(function(response) {
$log.debug('Token refreshed!');
if(angular.isUndefined(response.data) || angular.isUndefined(response.data.access_token))
{
$log.debug('Error while trying to refresh token!');
uncacheSession();
}
else {
SessionService.set('access_token', response.data.access_token);
SessionService.set('token_type', response.data.token_type);
SessionService.set('expires', tokens.expires);
SessionService.set('expires_in', response.data.expires_in);
deferred.resolve(response.data.access_token);
}
}, function() {
// Error
$log.debug('Error while trying to refresh token!');
uncacheSession();
});
}
return deferred.promise;
}
};
});
As a final note, keeping track of both inflight getToken requests and inflight refreshToken requests will keep you from making too many calls to your server. Under high load you might be creating way more access tokens than you need.
UPDATE 2:
Also, reviewing the code, when you get a 401 error you are calling refreshToken(). However, refreshToken does not put the new token information in the session cache, so new requests are going to continue using the old token. Updated the Plunkr.

Categories

Resources