I copied the following code from the web to work with JWT authorisation, but it does not work. In particular the $location.path command has no impact - the redirect does not take place. I also tried with $state.go, but that led to bigger errors. I don't fully understand what $q is referring to here, not what is waiting for the promise to unwind, but the issue is $location.path not taking the user back to the login screen (nor are the proposals below with respect to $state changes).
$httpProvider.interceptors.push(function($q, $location, $localStorage) {
return {
'request': function (config) {
config.headers = config.headers || {};
if ($localStorage.token) {
config.headers.Authorization = 'Bearer ' + $localStorage.token;
}
return config;
},
'responseError': function(response) {
if(response.status === 401 || response.status === 403) {
console.log("app.js: httpInterceptor caught authorisation status response");
delete $localStorage.token;
$location.path('/'); // to login page
// $state.go('login');
}
return $q.reject(response);
}
};
});
}
To test, I send a message that creates an server error, catch that in devtools on the way back, and then manually set response.status = 403. The redirect is clearly parsed but does not lead to the redirect. I can see that the login screen is put back on the screen, but is then immediately overwritten by a different view.
This is the factory $http ajax request. Is it possible that the deferred $q I use here is interfering with that in the interceptor?
$http(httpObj)
.success(function (response) { // returns 0: mongo resto data, 1: wpserver report
console.log("%s succesful, response: %s", method, response);
if (!updateNotAdd) {
Restos.data.restos.push(response[0]); // add to local copy of data
} else {
// replace existing entry with new information
var idxToReplace = _.findIndex(Restos.data.restos, function(r) {
return r.qname === resto.qname;
});
// copy over all data from editor model to database
_.assign(Restos.data.restos[idxToReplace], resto);
}
var response = {
success: true,
id: response[0]._id,
message: response[1]
};
$rootScope.$broadcast("ajaxresponse", response);
deferred.resolve(response);
})
.error(function (msg) {
// console.log(msg);
var response = {
success: false,
message: msg
};
$rootScope.$broadcast("ajaxresponse", response);
deferred.resolve(response);
});
return deferred.promise;
You shouldn't have an issue leveraging ui-router here. You need to use the $injector service to get a reference on the $state service. Observe the following change...
$httpProvider.interceptors.push(function($q, $location, $localStorage, $injector) {
return {
'request': function (config) {
config.headers = config.headers || {};
if ($localStorage.token) {
config.headers.Authorization = 'Bearer ' + $localStorage.token;
}
return config;
},
'responseError': function(response) {
if(response.status === 401 || response.status === 403) {
console.log("app.js: httpInterceptor caught authorisation status response");
delete $localStorage.token;
$injector.get('$state').go('login');
}
return $q.reject(response);
}
};
});
}
The bigger issues you're experiencing are caused by a circular dependency due to ui-router injecting $http into $TemplateFactory - leading to a circular reference to $http inside $httpProvider when you attempt to inject $state (which doesn't yet appear to be in your interceptors injection signature anyways)
Not sure whether you want to use, but probably this would redirect:
$window.location.href = url;
Related
I'm using Angular 1.6.4, Express 4.15.2 and express-session.
I am trying to catch if the user is unauthorized to access a certain route by checking the existence of req.session.user parameter. If he's not, I'd like to send a 401 response status and change the state in Angular.
The problem is that I am not getting any response object to check the status of.
I have tried using an interceptor, logging out error.response.body, logging out everything really to find out where it is that I'm losing the response object.
Here's some code, any help would be greatly appreciated!
express:
app.get('/update', sessionCheck, function(req, res) {
res.send('session');
});
function sessionCheck(req, res, next){
if(req.session.user) {
next();
} else {
console.log('before');
return res.status(401).send('Unauthorized');
console.log('after');
}
}
angular:
.state('update', {
url: '/update',
views: {
"": {
templateUrl: 'templates/update.html',
controller: function($http) {
return $http.get('/update').then(function(response) {
console.log('Ok response' + response);
}, function(error) {
console.log('Error response' + error.response.body);
});
},
},
"carousel": {
templateUrl: "templates/carousel.html"
},
"footer": {
templateUrl: "templates/footer.html"
}
}
})
network screen
Have you tried to do this using an interceptor?
You can try in this way:
anyModule.service('yourInterceptor', function($q) {
var service = this;
service.responseError = function(response) {
if (response.status == 401){
//do something
}
return $q.reject(response);
};
})
Note that here we are dealing with responseError.
You need to register you interceptor too in a config function:
$httpProvider.interceptors.push('yourInterceptor');
You can see this for more information about this interceptor:
Capture HTTP 401 with Angular.js interceptor
UPDATE:
You can register an interceptor in this way too:
app.factory("YourInterceptor", ["$q", "$rootScope", "$location",
function($q, $rootScope, $location) {
var success = function(response) {
//do something
return response;
},
error = function(response) {
if(response.status === 401) {
// do something
}
return $q.reject(response); //reject on error
};
return function(httpPromise) {
return httpPromise.then(success, error);
};
}
Then you register your interceptor in this way (in a config of the module):
$httpProvider.responseInterceptors.push("YourInterceptor");
Note that you push the interceptor in responseInterceptors. This work to me.
I have interceptor, however it all responses only goes into request(), even if 500 code returned
Here is my code
angular.module("BusinessTool").factory('InterceptorService',['$q', '$location', function( $q, $location, $http ){
resp = $.get('api/me.json');
return {
request: function(config){
// everything is ok
return config;
},
responseError: function(rejection) {
//error here. for example server respond with 401
if (resp.status == 401) {
window.location.href = '/';
}
return $q.reject(rejection);
}
}
}]);
angular.module("BusinessTool").config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('InterceptorService');
}]);
I want to redirect to / url in case of 401. Also is it possible to force interceptor to do request to api/me.json before each request?
I'm using AngularJs with JWT Auth. I've already made an api and I've test it.
With every request the api expect a token and with every response it gives a token object back. But when I try it like this:
employeeAppModule.config([
'$httpProvider',
function ($httpProvider) {
$httpProvider.interceptors.push(function () {
var token, headers, $cookies;
//inject cookies
angular.injector(['ngCookies']).invoke(['$cookies', function(_$cookies_) {
$cookies = _$cookies_;
}]);
return {
request: function (request) {
token = $cookies.get('jwt-token');
headers = request.headers || (request.headers = {});
if(token != null && token != 'undefined') {
headers.Authorization = token;
}
return request;
},
response: function (response) {
if (typeof response.data.result === 'undefined') {
return response;
}
if(response.status && response.status.code === 401) {
alert('token wordt verwijderd');
}
if(response.data && response.data.result.token && response.data.result.token.length > 10) {
$cookies.put('jwt-token', 'Bearer ' + response.data.result.token);
}
return response;
}
};
});
}]);
It's not working. If I put a couple of alert boxes between the request/ response it's working. How could I fix this problem?
You're setting the Authorization property on the headers variable and then you return request.
request.headers = headers;
return request;
This should solve the problem.
A couple of things though:
Don't put Bearer in the cookie itself, just add it to your header on every request, it's more elegant.
I'm not sure whether it's intended, but you really shouldn't return a token object on every single request. If it's the intended behavior, then my bad!
I'm setting up authorization with AngularJS and angular ui router.
If a user tries to access a route which requires authorization, the server returns a 401 status code.
I have created an HTTP response interceptor which checks for the 401 status code.
If the code is 401, it is supposed to redirect to the log in page, but this does not work.
It catches the 401, but does not redirect. Here is the code for the interceptor:
app.config(['$httpProvider', function ($httpProvider) {
// This is just for to check for XHR requests on the server
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
$httpProvider.responseInterceptors.push(['$q', '$location', function ($q, $location) {
return function (promise) {
return promise.then(
// Success: just return the response
function (response) {
return response;
},
// Error: check the error status to get only the 401
function (response) {
if (response.status === 401)
$location.url('/users/login');
return $q.reject(response);
}
);
}
}]);
}]);
EDIT: It seems to be working if I do it like this:
$timeout(function() { $location.url('/users/login'); }, 0);
I guess that puts it last in the execution stack and changes the execution context. Does anyone know more about this, or if this indeed does work or only seems so?
I add the same issue. So I changed my code to use the $location.path (not url)
$location.path("/users/login");
Can you try that
angular.module('yourApp').config(function ($httpProvider) {
var logsOutUserOn401 = ['$q', '$location', function ($q, $location) {
var success = function (response) {
return response;
};
var error = function (response) {
if (response.status === 401) {
//redirect them back to login page
$location.path('/login');
return $q.reject(response);
}
else {
return $q.reject(response);
}
};
return function (promise) {
return promise.then(success, error);
};
}];
$httpProvider.responseInterceptors.push(logsOutUserOn401);
});
(source : http://arthur.gonigberg.com/2013/06/29/angularjs-role-based-auth/)
I have it working with
$window.location.href='...'
I finally solved it, but I still don't know why it didn't work. Anyway, I had to use this to redirect: (state transition)
$injector.get('$state').transitionTo('users.login');
this is how we added a response interceptor in our project. its working for us.
$httpProvider.interceptors.push(function($q, $rootScope, $location,
$cookieStore) {
return {
'responseError' : function(
rejection) {
var status = rejection.status;
var config = rejection.config;
var method = config.method;
var url = config.url;
if (status == 401) {
$rootScope.shouldRedirect = true;
$rootScope.urlAttempted = url;
$location
.path("/login");
} else if (status == 403) {
$location
.path("/accessForbidden");
} else {
}
return $q.reject(rejection);
}
};
});
In my SPA I catch every 401 response from REST requests. From there I don't redirect to login page immediatly, but first I check to the backend if the problem is that the token has expired or not. If not (the user is not known) I redirect to login, but if it was an expired problem, I generate a new token then I run again the request that previously failed into a 401. Here is the code for my interceptor:
var $http, loginService;
return function (promise) {
return promise.then(function (response) {
return response;
}, function (response) {
if (response.status === 401) {
$http = $http || $injector.get('$http');
loginService = loginService || $injector.get('loginService');
var defer = $q.defer();
var promiseToken = defer.promise;
var configPreviousRequest = response.config;
console.log(configPreviousRequest);
var url = configurationService.serverUrl + "mobile" + configurationService.apiVersion + "/verify";
var request = $http.post(url, {'code': loginService.getVmmToken()});
// Get the token. If success, we try to login again
return request.then(
function (responseVerify) {
loginService.setVmmsToken(responseVerify.data);
loginService.setAuthentificationToken();
configPreviousRequest.headers.vmmsToken = responseVerify.data;
return $http(configPreviousRequest);
},
function () {
$location.path('/login');
});
}
return $q.reject(response);
});
};
But here is the result in Chrome Network tool. All methods are not in the correct number (called too many times for /verify and /blocks)
So I logged (with console.log(configPreviousRequest);) to see what happens. Here are logs:
We clearly observe that for one 401 error, I intercept it many times.
And I have no clue why :)
Has someone any idea?
Thanks