Reputation: 999
I feel that I'm doing something wrong. I'm new to Angular and I'm writing a fair bit of code to track logged in state. And the code feels rather brittle.
In my app I want to record whether I've authenticated or not. Right now I use a global variable to do this. But since this is updated asynchronously in the top level controller via a $http.get powered factory call which updates a plain old regular javascript variable, various directives and controllers don't get the correct state.
Is there a more angular way to do this?
Perhaps controllers/directives that care about the logged on state should have listeners and then a base controller should do a $scope.$broadcast so that they're aware of the state change? Or perhaps this is a good time to learn about $watch?
Plus I'm tracking this in the top-level controller. Perhaps that's the wrong way to do this? Might tracking logged in state be done better as a provider or a service?
Upvotes: 1
Views: 196
Reputation: 152
In angular, all .service and .factory types are singletons. This means that you can add your authentication logic into a .service or .factory and the data you store on that object will be accessible to any other service/factory or controller throughout the system. So, put your auth logic on a service, something like this:
angular.module('my.app.services', []).factory('AuthService', ['$http', function($http) {
var authenticated = false;
var authUrl = '/path/to/auth/handler';
return {
isAuthenticated: function(){
return authenticated;
},
login: function(data) {
return $http.post(authUrl, data).then(function(response) {
authenticated = true;
return authenticated;
}).catch(function(err) {
authenticated = false;
return authenticated;
});
}
};
}]);
So, now you can inject AuthService anywhere and call AuthService.isAuthenticated() to get the boolean.
Also, you ideally want to use something like a session cookie to ensure all the rest of the requests to the application are secured. Basically, every request to the server must be authenticated. When the server detects a request that is not authenticated, you can return an HTTP 403 status code from the server. Now, you will need a clean way to handle a 403 error from ANY REQUEST made via $http or $resource. For this, you can setup an HTTP interceptor, which will post process any failed request with status code 4XX or 5XX.
In your app's config function:
angular.module('yourAppName').config(function ($provide, $httpProvider) {
//Create interceptor to intercept all $http and $resource calls
$provide.factory('MyHttpErrorInterceptor', ['$window', '$q', function ($window, $q) {
function notifyError(rejection) {
console.log(rejection);
var errorMessage = '';
switch(rejection.status) {
case 403:
//redirect to auth URL when ANY request returns a 403 - Not authorized
$window.location = 'some/path/to/auth/resource';
break;
case 500:
//here, handle any 500 error, which means the server through an exception in the code.
// handle rejection.data to get any server side error messages
// errorMessage += rejection.data.message;
break;
case 400:
//handle bad request params
// errorMessage += something from rejection.data
break;
default:
//handle any other status code such as -1 which could occur on things like a cross origin request
errorMessage += 'Request to ' + rejection.config.url + ' failed.';
}
if(rejection.status !== 403) {
//don't show errors on 403. Should redirect to login page.
//perhaps pop up an error page or message here, with the
// errorMessage attribute
}
}
return {
// handle request failure
requestError: function (rejection) {
//handle failed request
notifyError(rejection);
// important to reject the promise
return $q.reject(rejection);
},
// handle response error statuses
responseError: function (rejection) {
//handle error status and data
notifyError(rejection);
//important to reject the promise
return $q.reject(rejection);
}
};
}]);
// Add the interceptor to the $httpProvider.
$httpProvider.interceptors.push('MyHttpErrorInterceptor');
});
Note, you can use the interceptor to also transform requests and response too, so if you find yourself needing to process $http requests or responses globally in the system, use the interceptor and define the logic in one place.
Hopefully, this helps get you going :)
Upvotes: 2