Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

AngularJS Security - Authorization on Angular Routes

4.83/5 (18 votes)
26 Aug 2014CPOL4 min read 157.9K  
Securing routes in Angular is not available out of the box. This tip would help in securing routes in Angular based applications.

Introduction

AngularJS has come a long way since its introduction. It is a comprehensive JavaScript framework for Single Page Application (SPA) development. It has some awesome features like 2-way binding, directives, etc. This topic will focus on Angular security. This is client side security which you can implement with Angular. In addition to client side route security (which we would discuss in this tip), you would need to secure access at server side as well. Client side security helps in avoiding extra round trips to server after making user session object at client. However, if someone tricks the browser, then server side security should be able to reject unauthorized access. In this tip, I would restrict my discussion to client side security.

We would make one authorization service, which would fetch the User authorization/role specific data from server about a user role. And, later, this user role data would be reused for all routes visited by user, during his session. A very interesting aspect which you would notice about angular service is that, service data is persisted throughout the user session. In this example, I have considered only Windows Authentication, for sake of simplicity. To understand this post, you need basic knowledge of AngularJS, Angular Routing and promises. I will walk you through, step by step, on the details of the implementation. It is not something I have invented new in angularJS. However, I have tried to put together the things which are available in Angular and can be combined to make a re-usable security code. I am open to suggestions for this article write-up improvement. Please let me know if you see anything which is incorrect or missing. I will make the corrections as necessary. Hope you enjoy reading it!

Step 1: Define Global Variables in app-module

Define roles for the application:

These are the roles which you would like to keep for authorization in the application. It is necessary to keep them global as these would be used by both at App module scope and service scope.

JavaScript
var roles = {
      superUser: 0,
      admin: 1,
      user: 2
  };

Define route for unauthorized access for the application:

This is the route where you would want the user to go, if he is not authorized to see any page.

We would be using this variable later, in the App module routes.

JavaScript
var routeForUnauthorizedAccess = '/SomeAngularRouteForUnauthorizedAccess';

Step 2: Define the Service for Authorization

This is the heart of the whole authorization. Angular service (or factory) is singleton and persisted across different routes of application, for a given user session. We can leverage this feature of service to do authorization on different routes. In order for you to create the service, you would need a service (like Web API) at server side, which can give you a set of roles for a given user. Once roles are procured from server side, then Angular service can persist this knowledge to be re-used for all routes, during the session of user. I have tried to explain the service code below with inline comments.

JavaScript
appModule.factory('authorizationService', function ($resource, $q, $rootScope, $location) {
    return {
        // We would cache the permission for the session,
        //to avoid roundtrip to server
        //for subsequent requests

        permissionModel: {
            permission: {},
            isPermissionLoaded: false
        },

        permissionCheck: function (roleCollection) {

            // we will return a promise .
            var deferred = $q.defer();

            //this is just to keep a pointer to parent scope from within promise scope.
            var parentPointer = this;

            //Checking if permission object(list of roles for logged in user) 
            //is already filled from service
            if (this.permissionModel.isPermissionLoaded) {
                //Check if the current user has required role to access the route
                this.getPermission(this.permissionModel, roleCollection, deferred);
            } else {
                //if permission is not obtained yet, we will get it from  server.
                // 'api/permissionService' is the path of server web service , used for this example.

                $resource('/api/permissionService').get().$promise.then(function (response) {
                    //when server service responds then we will fill the permission object
                    parentPointer.permissionModel.permission = response;

                    //Indicator is set to true that permission object is filled and 
                    //can be re-used for subsequent route request for the session of the user
                    parentPointer.permissionModel.isPermissionLoaded = true;

                    //Check if the current user has required role to access the route
                    parentPointer.getPermission(parentPointer.permissionModel, roleCollection, deferred);
                });
            }
            return deferred.promise;
        },

        //Method to check if the current user has required role to access the route
        //'permissionModel' has permission information obtained from server for current user
        //'roleCollection' is the list of roles which are authorized to access route
        //'deferred' is the object through which we shall resolve promise
        getPermission: function (permissionModel, roleCollection, deferred) {
            var ifPermissionPassed = false;

            angular.forEach(roleCollection, function (role) {
                switch (role) {
                    case roles.superUser:
                        if (permissionModel.permission.isSuperUser) {
                            ifPermissionPassed = true;
                        }
                        break;
                    case roles.admin:
                        if (permissionModel.permission.isAdministrator) {
                            ifPermissionPassed = true;
                        }
                        break;
                    case roles.user:
                        if (permissionModel.permission.isUser) {
                            ifPermissionPassed = true;
                        }
                        break;
                    default:
                        ifPermissionPassed = false;
                }
            });
            if (!ifPermissionPassed) {
                //If user does not have required access, 
                //we will route the user to unauthorized access page
                $location.path(routeForUnauthorizedAccess);
                //As there could be some delay when location change event happens, 
                //we will keep a watch on $locationChangeSuccess event
                // and would resolve promise when this event occurs.
                $rootScope.$on('$locationChangeSuccess', function (next, current) {
                    deferred.resolve();
                });
            } else {
                deferred.resolve();
            }
        }
    };
});

Step 3: Use Security in Routing

Let's use all our hard work done so far to secure the routes. So far, we created a service which is capable of checking if a user falls under a particular role or not. This knowledge can be used in the angular routes now. In the angular module, where we define routes, we can put these checks to see if the user has access to any of the roles specified for a route. I have put combinations of roles in these routes, to ensure better understanding.

JavaScript
var appModule = angular.module("appModule", ['ngRoute', 'ngResource'])
.config(function($routeProvider, $locationProvider) {
    $routeProvider
        .when('/superUserSpecificRoute', {
            templateUrl: '/templates/superUser.html', //path of the view/template of route
            caseInsensitiveMatch: true,
            controller: 'superUserController', //controller which would be used for the route
            resolve: { //Here we would use all the hardwork we have done
                //above and make call to the authorization Service
                //resolve is a great feature in angular, which ensures that a route
                //controller (in this case superUserController ) is invoked for a route
                //only after the promises mentioned under it are resolved.
                permission: function(authorizationService, $route) {
                    return authorizationService.permissionCheck([roles.superUser]);
                },
            }
        })
        .when('/userSpecificRoute', {
            templateUrl: '/templates/user.html',
            caseInsensitiveMatch: true,
            controller: 'userController',
            resolve: {
                permission: function(authorizationService, $route) {
                    return authorizationService.permissionCheck([roles.user]);
                },
            }
        })
        .when('/adminSpecificRoute', {
            templateUrl: '/templates/admin.html',
            caseInsensitiveMatch: true,
            controller: 'adminController',
            resolve: {
                permission: function(authorizationService, $route) {
                    return authorizationService.permissionCheck([roles.admin]);
                },
            }
        })
        .when('/adminSuperUserSpecificRoute', {
            templateUrl: '/templates/adminSuperUser.html',
            caseInsensitiveMatch: true,
            controller: 'adminSuperUserController',
            resolve: {
                permission: function(authorizationService, $route) {
                    return authorizationService.permissionCheck([roles.admin, roles.superUser]);
                },
            }
        })
        // Route for unauthorized access (When permission is not given to visit a page)
        .when(routeForUnauthorizedAccess,
           {
            templateUrl: '/templates/UnauthorizedAccess.html',

           caseInsensitiveMatch: true
         })
});

Summary

We saw in this tip as to how we can define authorization on various routes of Angular JS based application. As an alternative to securing routes, you may choose to not show the link (Or Menu links) to user who does not have access to it, however, putting the access on route itself at a central place in application ensures that even if you miss hiding a link (which ultimately becomes a route) for unauthorized user, the route configuration will ensure that unauthorized user will not be taken to the route.

Lastly, but importantly, I would like to emphasize that, all of the above stands invalid if the user tricks the browser. Therefore, it is important to have server side check for any read/update of data.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)