AngularJS Authentication + RESTful API AngularJS Authentication + RESTful API angularjs angularjs

AngularJS Authentication + RESTful API


This is taken from my blog post on url route authorisation and element security here but I will briefly summaries the main points :-)

Security in frontend web application is merely a starting measure to stop Joe Public, however any user with some web knowledge can circumvent it so you should always have security server-side as well.

The main concern around security stuff in angular is route security, luckily when defining a route in angular you are create an object, an object that can have other properties. The cornerstone to my approach is to add a security object to this route object which basically defines the roles the user must be in to be able to access a particular route.

 // route which requires the user to be logged in and have the 'Admin' or 'UserManager' permission    $routeProvider.when('/admin/users', {        controller: 'userListCtrl',        templateUrl: 'js/modules/admin/html/users.tmpl.html',        access: {            requiresLogin: true,            requiredPermissions: ['Admin', 'UserManager'],            permissionType: 'AtLeastOne'        });

The whole approach focuses around an authorisation service which basically does the check to see if the user has the required permissions. This service abstract the concerns away from the other parts of this solution to do with the user and their actual permission that would have been retrieved from the server during login. While the code is quite verbose it is fully explained in my blog post. However, it basically handle the permission check and two modes of authorisation. The first is that the user must have at least on of the defined permissions, the second is the user must have all of the defined permissions.

angular.module(jcs.modules.auth.name).factory(jcs.modules.auth.services.authorization, [  'authentication',  function (authentication) {   var authorize = function (loginRequired, requiredPermissions, permissionCheckType) {    var result = jcs.modules.auth.enums.authorised.authorised,        user = authentication.getCurrentLoginUser(),        loweredPermissions = [],        hasPermission = true,        permission, i;    permissionCheckType = permissionCheckType || jcs.modules.auth.enums.permissionCheckType.atLeastOne;    if (loginRequired === true && user === undefined) {        result = jcs.modules.auth.enums.authorised.loginRequired;    } else if ((loginRequired === true && user !== undefined) &&        (requiredPermissions === undefined || requiredPermissions.length === 0)) {        // Login is required but no specific permissions are specified.        result = jcs.modules.auth.enums.authorised.authorised;    } else if (requiredPermissions) {        loweredPermissions = [];        angular.forEach(user.permissions, function (permission) {            loweredPermissions.push(permission.toLowerCase());        });        for (i = 0; i < requiredPermissions.length; i += 1) {            permission = requiredPermissions[i].toLowerCase();            if (permissionCheckType === jcs.modules.auth.enums.permissionCheckType.combinationRequired) {                hasPermission = hasPermission && loweredPermissions.indexOf(permission) > -1;                // if all the permissions are required and hasPermission is false there is no point carrying on                if (hasPermission === false) {                    break;                }            } else if (permissionCheckType === jcs.modules.auth.enums.permissionCheckType.atLeastOne) {                hasPermission = loweredPermissions.indexOf(permission) > -1;                // if we only need one of the permissions and we have it there is no point carrying on                if (hasPermission) {                    break;                }            }        }        result = hasPermission ?                 jcs.modules.auth.enums.authorised.authorised :                 jcs.modules.auth.enums.authorised.notAuthorised;    }    return result;};

Now that a route has security you need a way of determining if a user can access the route when a route change has been started. To do this we be intercepting the route change request, examining the route object (with our new access object on it) and if the user cannot access the view we replace the route with another one.

angular.module(jcs.modules.auth.name).run([      '$rootScope',    '$location',    jcs.modules.auth.services.authorization,    function ($rootScope, $location, authorization) {        $rootScope.$on('$routeChangeStart', function (event, next) {            var authorised;            if (next.access !== undefined) {                authorised = authorization.authorize(next.access.loginRequired,                                                     next.access.permissions,                                                     next.access.permissionCheckType);                if (authorised === jcs.modules.auth.enums.authorised.loginRequired) {                    $location.path(jcs.modules.auth.routes.login);                } else if (authorised === jcs.modules.auth.enums.authorised.notAuthorised) {                    $location.path(jcs.modules.auth.routes.notAuthorised).replace();                }            }        });    }]);

The key here really is the '.replace()' as this replace the current route (the one they have not got rights to see) with the route we are redirecting them to. This stop any then navigating back to the unauthorised route.

Now we can intercept routes we can do quite a few cool things including redirecting after a login if a user landed on a route that they needed to be logged in for.

The second part of the solution is being able to hide/show UI element to the user depending on there rights. This is achieve via a simple directive.

angular.module(jcs.modules.auth.name).directive('access', [          jcs.modules.auth.services.authorization,        function (authorization) {            return {              restrict: 'A',              link: function (scope, element, attrs) {                  var makeVisible = function () {                          element.removeClass('hidden');                      },                      makeHidden = function () {                          element.addClass('hidden');                      },                      determineVisibility = function (resetFirst) {                          var result;                          if (resetFirst) {                              makeVisible();                          }                          result = authorization.authorize(true, roles, attrs.accessPermissionType);                          if (result === jcs.modules.auth.enums.authorised.authorised) {                              makeVisible();                          } else {                              makeHidden();                          }                      },                      roles = attrs.access.split(',');                  if (roles.length > 0) {                      determineVisibility(true);                  }              }            };        }]);

You would then sure an element like so:

 <button type="button" access="CanEditUser, Admin" access-permission-type="AtLeastOne">Save User</button>

Read my full blog post for a much more detailed overview to the approach.


I haven't been using $resource because I'm just hand crafting my service calls for my application. That said I've handled login by having a service which depends on all the other services that get some sort of initialization data. When the login succeeds it triggers for initialization of all the services.

Within my controller scope I watch the loginServiceInformation and populate some properties of the model accordingly (to trigger the appropriate ng-show/hide). With regard to routing I'm using Angular's built in routing and I simply have an ng-hide based on the loggedIn boolean shown here, it shows text to request login or else the div with the ng-view attribute (so if not logged in immediately after login you're on the correct page, currently I load data for all views but I believe this could be more selective if necessary)

//Servicesangular.module("loginModule.services", ["gardenModule.services",                                        "surveyModule.services",                                        "userModule.services",                                        "cropModule.services"                                        ]).service(                                            'loginService',                                            [   "$http",                                                "$q",                                                "gardenService",                                                "surveyService",                                                "userService",                                                "cropService",                                                function (  $http,                                                            $q,                                                            gardenService,                                                            surveyService,                                                            userService,                                                            cropService) {    var service = {        loginInformation: {loggedIn:false, username: undefined, loginAttemptFailed:false, loggedInUser: {}, loadingData:false},        getLoggedInUser:function(username, password)        {            service.loginInformation.loadingData = true;            var deferred = $q.defer();            $http.get("php/login/getLoggedInUser.php").success(function(data){                service.loginInformation.loggedIn = true;                service.loginInformation.loginAttemptFailed = false;                service.loginInformation.loggedInUser = data;                gardenService.initialize();                surveyService.initialize();                userService.initialize();                cropService.initialize();                service.loginInformation.loadingData = false;                deferred.resolve(data);            }).error(function(error) {                service.loginInformation.loggedIn = false;                deferred.reject(error);            });            return deferred.promise;        },        login:function(username, password)        {            var deferred = $q.defer();            $http.post("php/login/login.php", {username:username, password:password}).success(function(data){                service.loginInformation.loggedInUser = data;                service.loginInformation.loggedIn = true;                service.loginInformation.loginAttemptFailed = false;                gardenService.initialize();                surveyService.initialize();                userService.initialize();                cropService.initialize();                deferred.resolve(data);            }).error(function(error) {                service.loginInformation.loggedInUser = {};                service.loginInformation.loggedIn = false;                service.loginInformation.loginAttemptFailed = true;                deferred.reject(error);            });            return deferred.promise;        },        logout:function()        {            var deferred = $q.defer();            $http.post("php/login/logout.php").then(function(data){                service.loginInformation.loggedInUser = {};                service.loginInformation.loggedIn = false;                deferred.resolve(data);            }, function(error) {                service.loginInformation.loggedInUser = {};                service.loginInformation.loggedIn = false;                deferred.reject(error);            });            return deferred.promise;        }    };    service.getLoggedInUser();    return service;}]);//Controllersangular.module("loginModule.controllers", ['loginModule.services']).controller("LoginCtrl", ["$scope", "$location", "loginService", function($scope, $location, loginService){    $scope.loginModel = {                        loadingData:true,                        inputUsername: undefined,                        inputPassword: undefined,                        curLoginUrl:"partials/login/default.html",                        loginFailed:false,                        loginServiceInformation:{}                        };    $scope.login = function(username, password) {        loginService.login(username,password).then(function(data){            $scope.loginModel.curLoginUrl = "partials/login/logoutButton.html";        });    }    $scope.logout = function(username, password) {        loginService.logout().then(function(data){            $scope.loginModel.curLoginUrl = "partials/login/default.html";            $scope.loginModel.inputPassword = undefined;            $scope.loginModel.inputUsername = undefined;            $location.path("home");        });    }    $scope.switchUser = function(username, password) {        loginService.logout().then(function(data){            $scope.loginModel.curLoginUrl = "partials/login/loginForm.html";            $scope.loginModel.inputPassword = undefined;            $scope.loginModel.inputUsername = undefined;        });    }    $scope.showLoginForm = function() {        $scope.loginModel.curLoginUrl = "partials/login/loginForm.html";    }    $scope.hideLoginForm = function() {        $scope.loginModel.curLoginUrl = "partials/login/default.html";    }    $scope.$watch(function(){return loginService.loginInformation}, function(newVal) {        $scope.loginModel.loginServiceInformation = newVal;        if(newVal.loggedIn)        {            $scope.loginModel.curLoginUrl = "partials/login/logoutButton.html";        }    }, true);}]);angular.module("loginModule", ["loginModule.services", "loginModule.controllers"]);

The HTML

<div style="height:40px;z-index:200;position:relative">    <div class="well">        <form            ng-submit="login(loginModel.inputUsername, loginModel.inputPassword)">            <input                type="text"                ng-model="loginModel.inputUsername"                placeholder="Username"/><br/>            <input                type="password"                ng-model="loginModel.inputPassword"                placeholder="Password"/><br/>            <button                class="btn btn-primary">Submit</button>            <button                class="btn"                ng-click="hideLoginForm()">Cancel</button>        </form>        <div            ng-show="loginModel.loginServiceInformation.loginAttemptFailed">            Login attempt failed        </div>    </div></div>

The Base HTML that uses the parts above to complete the picture:

<body ng-controller="NavigationCtrl" ng-init="initialize()">        <div id="outerContainer" ng-controller="LoginCtrl">            <div style="height:20px"></div>            <ng-include src="'partials/header.html'"></ng-include>            <div  id="contentRegion">                <div ng-hide="loginModel.loginServiceInformation.loggedIn">Please login to continue.                <br/><br/>                This new version of this site is currently under construction.                <br/><br/>                If you need the legacy site and database <a href="legacy/">click here.</a></div>                <div ng-view ng-show="loginModel.loginServiceInformation.loggedIn"></div>            </div>            <div class="clear"></div>            <ng-include src="'partials/footer.html'"></ng-include>        </div>    </body>

I have the login controller defined with an ng-controller higher up in the DOM so that I can change the body area of my page based on the loggedIn variable.

Note I haven't implemented form validation here yet. Also admittedly still quite fresh to Angular so any pointers to things in this post are welcome. Although this doesn't answer the question directly since it isn't a RESTful based implementation I believe the same can be adapted to $resources since it's built on top of $http calls.


I've written an AngularJS module for UserApp that does pretty much everything you ask for. You could either:

  1. Modify the module and attach the functions to your own API, or
  2. Use the module together with the user management API, UserApp

https://github.com/userapp-io/userapp-angular

It supports protected/public routes, rerouting on login/logout, heartbeats for status checks, stores the session token in a cookie, events, etc.

If you want to give UserApp a try, take the course on Codecademy.

Here's some examples of how it works:

  • Login form with error handling:

    <form ua-login ua-error="error-msg">    <input name="login" placeholder="Username"><br>    <input name="password" placeholder="Password" type="password"><br>    <button type="submit">Log in</button>    <p id="error-msg"></p></form>
  • Signup form with error handling:

    <form ua-signup ua-error="error-msg">  <input name="first_name" placeholder="Your name"><br>  <input name="login" ua-is-email placeholder="Email"><br>  <input name="password" placeholder="Password" type="password"><br>  <button type="submit">Create account</button>  <p id="error-msg"></p></form>
  • How to specify which routes that should be public, and which route that is the login form:

    $routeProvider.when('/login', {templateUrl: 'partials/login.html', public: true, login: true});$routeProvider.when('/signup', {templateUrl: 'partials/signup.html', public: true});

    The .otherwise() route should be set to where you want your users to be redirected after login. Example:

    $routeProvider.otherwise({redirectTo: '/home'});

  • Log out link:

    <a href="#" ua-logout>Log Out</a>

    (Ends the session and redirects to the login route)

  • Access user properties:

    User info is accessed using the user service, e.g: user.current.email

    Or in the template: <span>{{ user.email }}</span>

  • Hide elements that should only be visible when logged in:

    <div ng-show="user.authorized">Welcome {{ user.first_name }}!</div>

  • Show an element based on permissions:

    <div ua-has-permission="admin">You are an admin</div>

And to authenticate to your back-end services, just use user.token() to get the session token and send it with the AJAX request. At the back-end, use the UserApp API (if you use UserApp) to check if the token is valid or not.

If you need any help, just let me know :)