AngularJs Global Message Service and Authentication - angularjs

I have a message system in place that has two methods show and clear. I want to use $http interceptors in order to handle authentication because my server is smart enough to know whether a user is authenticated or not per request and will tell me so by sending me me a status response of 401 or 403. So, I have something like this for my interceptor:
myModule.factory('statusInterceptor', function($location, MessageService, urlManager){
return {
responseError: function(response){
switch(response.status){
...
case 401:
case 403:
$location.url(urlManager.reverse('Login'));
MessageService.show('Dude, you need to log in first.');
break;
case 500:
break;
...
}
return response;
}
};
});
That works just fine on either a 401 or a 403 response as it shows the message as expected. The problem I'm having is clearing the message whenever the user logs in or goes to another route. I have a MainCtrl that is in charge of almost everything and one of the things that it is looking after is $routeChangeStart. My thought being that the view is changing to a different view, so I want to clear the messages at the beginning of the route switch. So, in my MainCtrl I have:
myControllers.controller('MainCtrl', function($scope, MessageService){
$scope.$on('$routeChangeStart', function(){
MessageService.clear();
});
});
Here's the way I expect the app to react:
A user has tried to do something without being authenticated by sending a request to the server
The server responds with either a 401 or 403 because the user is not authenticated.
The responseError function is fired calling my $location.url() method which, in my mind, should fire the $routeChangeStart, or even $locationChangeStart, events (I've tried both).
This should in turn fire my MessageService.clear() method and clear out any previous message.
The user is finally redirected to the login page with the correct "Dude, you need to log in first." message.
What really happens:
The user is redirected to the login page as expected, however, the error message does not display. When setting certain debug points in Chrome, I see that $location.path() is called immediately followed by MessageService.show('Dude...'), finally followed by the MessageService.clear() call.

Maybe you like this approach to that problem
http://jsfiddle.net/jYCPQ/1/
module.factory('authenticationInterceptor', function ($q, navigator, MessageService) {
return {
responseError: function (response) {
switch (response.status) {
case 403:
navigator.navigateTo("/login").then(function () {
MessageService.show('Dude, you need to log in first.');
});
return $q.reject(response);
}
return response;
}
};
});
module.factory("navigator", function ($location, $rootScope, $q) {
var navigateTo = function (url) {
var defered = $q.defer();
$location.url(url);
var unbind = $rootScope.$on('$routeChangeSuccess', function () {
defered.resolve();
unbind();
})
return defered.promise;
}
return {
navigateTo: navigateTo
}
});
regards

I finally found a solution and ended up with something like this:
In my interceptor function:
myModule.factory('statusInterceptor', function($rootScope){
return {
responseError: function(response){
switch(response.status){
...
case 401:
case 403:
$rootScope.$broadcast('event:notAuthenticated');
break;
case 500:
break;
...
}
return response;
}
};
});
And in my controller:
myControllers.controller('MainCtrl', function($scope, $location, MessageService){
$scope.$on('event:notAuthenticated', function(){
$location.path(urlManager.reverse('Login'));
var remove_this_binding = $scope.$on('$routeChangeSuccess', function(){
messageService.show('Dude, you need to log in first.');
remove_this_binding();
});
});
$scope.$on('$routeChangeStart', function(){
messageService.clear();
});
});
I was trying to find an angular way of having a callback associated with $location's path change and this is the best I could come up with. If anyone else has a better solution, I'm all ears.

Related

angularjs http interceptor to show error on loaded location path

I have an application for which I created an interceptor to handle token expirations after 15 minute inactivity, it successfully redirects to the login page after a token has expired, but Im not able to show the error after redirecting to the login page.
My question is, how can I show the user the token expired error on the login page, after the interceptor has redirected the app to that page.
Heres my redirector:
app
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(function($q, $location, LoopBackAuth) {
return {
responseError: function(rejection) {
if (rejection.status == 401) {
//Now clearing the loopback values from client browser for safe logout...
LoopBackAuth.clearUser();
LoopBackAuth.clearStorage();
$location.path("/login");
}
return $q.reject(rejection);
}
};
})
}])
.config(function(LoopBackResourceProvider) {    
LoopBackResourceProvider.setAuthHeader('X-Access-Token');
})
Finally and thanks to #forrestmid to point me in the right direction this is what I ended up doing.
on the http interceptor just added:
$location.path("/login").search({error: 'invalid_token'});
and then on the controller just had to do:
var queryString = $location.search();
$scope.errors = {};
if (queryString && queryString.error) {
$scope.errors = {
'invalid_token': {
code: 'invalid_token'
}
}
}
now on the template I already have logic to handle the error object so now it works fine :)
Referencing this post in regards to injecting the $state service into an HTTP interceptor:
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(function($q, $injector, LoopBackAuth) {
return {
responseError: function(rejection) {
if (rejection.status == 401) {
//Now clearing the loopback values from client browser for safe logout...
LoopBackAuth.clearUser();
LoopBackAuth.clearStorage();
$injector.get('$state').go('app.login', {error: 'Token expired.'});
}
return $q.reject(rejection);
}
};
})
}]);
Assuming that you're using ui.router:
app.config(function($stateProvider){
$stateProvider
.state("app",{abstract: true})
.state("app.login", {
url: "/login",
params: {error: ""}
});
});
By default there will be no error when transitioning to the app.login state, but when there is a param error set to whatever, it can display the error. This will be in the $stateParams.error variable on your login page.
Let me know if I botched any of that code since I didn't test it. The line I think you want is the $injector line.

How can I redirect a user to the login page using angularjs and laravel

I am working on a project using laravel and angularjs. I am using only Laravel to authenticate the users and when their logged in, then angularjs ui veiw will handle the navigation. when doing this I realized a problem, when the session has expire the user should be redirected to the logged in page based on the auth filter that is set on the route. Additionally when I checked the browser dev tool network tab, I see that the sign in page is send as a response. I am wondering how can I make my project redirect the user to the logged in page when the session has expire. how can I solve this problem and Thanks in advance for the assistance.
You can do that with $httpInterceptor, here is demo code:
var myApp = angular.module("MyApp", []);
myApp.config(function ($httpProvider, $provide) {
$provide.factory('myHttpInterceptor', function ($q, $location) {
return {
'response': function (response) {
//you can handle you sucess response here.
return response;
},
'responseError': function (rejection) {
console.log(rejection);
//if(rejection.data.xxx==="xxx")
if(rejection.status === 408){//session expired code
alert('logout!');
// clear your local data here...
$location.url("/login")
}
return $q.reject(rejection);
}
};
});
$httpProvider.interceptors.push('myHttpInterceptor');
});
myApp.controller("MainController", function ($scope, $http) {
$scope.response = {};
$scope.triggerGet = function () {
$http.get("/my/json").success(function (data) {
$scope.response = data;
});
};
});
When your server side response is session expired, you can handle the response.status or you can handle the other data with response.data.
Here is $httpInterceptor document.(In the middle of the page)
To redirect the user client-side in JavaScript use location. https://developer.mozilla.org/en-US/docs/Web/API/Location
For this case I think you want to look at location.assign() specifically.

Handle Angular 401 responses

I have simple api and a authorization point
when i request to api i get a 401 if the token is invalid (token loses validity past five minutes).
i know i can intercept 401 for example with
app.factory("HttpErrorInterceptorModule", ["$q", "$rootScope", "$location",
function($q, $rootScope, $location) {
var success = function(response) {
// pass through
return response;
},
error = function(response) {
if(response.status === 401) {
// dostuff
}
return $q.reject(response);
};
return function(httpPromise) {
return httpPromise.then(success, error);
};
}
]).config(["$httpProvider",
function($httpProvider) {
$httpProvider.responseInterceptors.push("HttpErrorInterceptorModule");
}
]);
but i want capture and queue the request and show a login form if is success then change the token (it's a header) and execute request again
You can use $httpInterceptor in slightly another way. If you want to redirect user after login to page where user actually failed you need to cache failed request in some service and then redirect user somewhere after login (I beleive in logic connected to your login).
But you may need to have some test endpoint to protect your controllers from unrestricted access, you might want to use resolve https://thinkster.io/egghead/resolve/
So in this case you will receive error connected with restricted access to proctedted endpoint but not to your page.
To solve this problem I used marker param (or header) to find out where I should redirect user after login.
Here is example of your httpInterceptor.
angular.factory('httpInterceptor', function ($q, $rootScope, $log, someService) {
return {
request: function (config) {
return config || $q.when(config)
},
response: function (response) {
return response || $q.when(response);
},
responseError: function (response) {
if (response.status === 401) {
//here I preserve login page
someService
.setRestrictedPageBeforeLogin(
extractPreservedInfoAboutPage(response)
)
$rootScope.$broadcast('error')
}
return $q.reject(response);
}
};
})
.config(function ($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
});
angular-http-auth module provides a service that intercepts requests and queques them to re-send them later once a user logs in.
This service fires also these events below, so you could listen to them and decide what to show on screen
event:auth-loginRequired
event:auth-loginCancelled
event:aut-loginConfirmed
Look at the code. It has just a few lines of code
https://github.com/witoldsz/angular-http-auth

AngularJS ngResource $save feedback

Hello I am using ngResource $save method and I get two different behaviours, I don't understand why
First I'm using it in this way:
$scope.user = new User($scope.user);
$scope.user.$save(function () {
$window.location.href = //redirection here;
}, function (response) {
$scope.form.addErrors(response.data.errors);
});
Then I have another controller when I'm doing a similar operation, but even getting 404 or 422 errors from the server the first callback is executed and the errors callback is ignored.
Does anyone have any idea of this? I've been searching in Google for hours trying to find more documentation about $save but I'm still stuck with this problem.
Thank you.
Well, the problem was on an interceptor I am using to detect 401 (unauthorized errors)
here is the interceptor, notice that you must return $q.reject(response) otherwise the other callbacks are not called (in my case the error callback in ngResource.$save)
MyApp.config(function ($httpProvider) {
$httpProvider.interceptors.push(function($window, $q) {
return {
'responseError': function(response) {
if (response.status == 401) { // Unathorized
$window.location.href = 'index.html';
}
// return response; <-- I was doing this before cancelling all errors
return $q.reject(response);
}
};
});
});

AngularJS Handle different error codes in different places

I have a service that contains a generic call to $http that all ajax calls uses. Here I have "centralized" error handling where the status codes results in redirects.
"FieldsSync" service:
return $http({
method: 'POST',
url: url,
data: $.extend(defaultPostData, postData)
}).error(function(data, status) {
switch (status) {
case 401: // "Unauthorized". Not logged in.
redirect.toLoginPage();
break;
case 403: // "Forbidden". Role has changed.
redirect.toLogoutPage();
break;
}
});
When calling one of the service functions from the controller I always return the deferred object to be able to hook up more error callbacks to be able to handle errors that should result in some kind of feedback for the user.
Controller:
fieldsSync.createField(newField).success(function(data) {
...
}).error(function(data, status) {
switch (status) { // <--- DO NOT WANT
case 401:
case 403:
return; // These errors are handled (=redirects) in the generic error callback and we don't want to show do anything while it redirects.
}
... // "Individual" error handling. =Displaying messages and stuff
});
But because I don't want error messages popping up before the redirect occurs I have to exit the error callback if the status codes already have been handled.
The question is:
How do i get rid of the switch cases in my controllers?
Is it possible to exit the chain of error callbacks when a specific error code has been handled? Or is it possible to work around this in a less ugly way? :)
This is a reoccurring problem for me, and my mind seems stuck.
I have checked the docs and cant find any pretty solution for this in either $http or $q.
I propose you to use a responseInterceptor to handle those cases:
http://docs.angularjs.org/api/ng.$http
I wrote the following, when I had the same "problem" as you. Its only a first draft, but you can improve it to your needs
InternalServerErrorResponseInterceptor = function($q, InternalServerErrorService) {
return function(promise) {
return promise.then(function(response) {
return response;
}, function(response) {
if (response.status > 499) {
InternalServerErrorService.handleError();
return $q.reject(response);
}
return $q.reject(response);
});
};
};
module.factory('InternalServerErrorResponseInterceptor', ['$q', 'InternalServerErrorService', InternalServerErrorResponseInterceptor]);
module.config(['$httpProvider', function($httpProvider) {
$httpProvider.responseInterceptors.push('InternalServerErrorResponseInterceptor');
}]);
Now your specific InternalServerErrorService can handle it the way you want it to be handled :)
regards
One solution I come up with is to redo the sync functions (puh!) so that they DONT'T return the deferred object. The functions inside the service could take the callbacks as arguments. I could then check if the error already has been handled by the generic error handling before it is called.
Pseudocode:
Service:
fieldsSync.createField(newField, successCallback, failCallback) {
...
genericErrorHandlerResultingInRedirects()
if (not 401 || 403) {
failCallback()
}
};
Controller:
fieldsSync.createField({}, function successCallback(){}, function failCallback(){
/* Handle errors that should give the user feedback */
});
Other solutions are most welcome.

Resources