Kevin Lyda
Kevin Lyda

Reputation: 999

Tracking "logged in" state in an AngularJS application

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

Answers (1)

agenaille
agenaille

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

Related Questions