Deal with users authentication in an AngularJS web app

This post is meant to share some thoughts about the main issues related to the user’s authentication in an AngularJS web app.

I will talk about how to maintain, and recognize, the status of authentication of an user (that is, if he’s logged in, or not) between the different routes of a web application. Moreover I will also explain how to handle the case of a not authenticated user, who is trying to access to a page, that requires the user to be logged in.

Before going into the details of my approach, it is very important to clarify that, because the user has full controll of the browser, each control implemented with front end technologies, must (!) be repeated also in the backend.

Recognize an authenticated user

There are probably several ways to recognize an authenticated user; infact it’s possible to set a global variable, or create a cookie… but my favourite way to reach the objective is to use an AngularJS service.

This approach give me several advantages.

  • The first advantage is strictly related to the real nature of each AngularJS service; services are singletons, so there is only one instance of each service… and this allow to share data between different views, controller, directives, filters, and others services, without the need to overpopulate the global scope.

  • The second thing that I like about using a service, is that it’s possible to use it also to store others informations about the user (when he is logged in). Just as example this is a simplified version of the service that I used to recognize if the user is already authenticated.

services.factory('UserService', [function() {
  var sdo = {
    isLogged: false,
    username: ''
  };
  return sdo;
}]);

Are you wondering about how to use this service?

Well the first step is without any doubt to inject it, everywhere it is needed; for example in the controller of the login route:

var ctrl = angular.module('myApp.controllers', []);

/* ... */

ctrl.controller('loginCtrl', ['$scope', '$http', 'UserService', 
  function(scope, $http, User) { /* ... */ }]);

In the loginCtrl controller, as you can see, it’s defined the login function.

Here for the sake of simplicity I omitted a lot of superfluous code… infact the most important thing to note is how I used the success, and error callback of the xhr request to set the properties of the UserService service.

/* ... */

scope.login = function() {
  // configuration object
  var config = { /* ... */ }

  $http(config)
  .success(function(data, status, headers, config) {
    if (data.status) {
      // succefull login
      User.isLogged = true;
      User.username = data.username;
    }
    else {
      User.isLogged = false;
      User.username = '';
    }
  })
  .error(function(data, status, headers, config) {
    User.isLogged = false;
    User.username = '';
  });
}

/* ... */

In this way, everywhere I inject the UserService service, its isLogged property tells me if the user is already authenticated or not.

Mantain reserved content… reserved

Every not trivial website out there has different type of pages; there are pages that everybody, even a casual visitor should be able to see, and others pages, which require the user to be logged, or to have particular administrative grant. For a web application, and in particular for a web app powered by Angularjs, the concept applies equally, just replace the word ‘page’, with the word ‘route’.

So now, it’s necessary a simple way to say who can see each route of our web app… at the end I come out with the following solution.

The $routeProvider is used to configure routes, and this is really simple with AngularJS:

app.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/login', { 
    templateUrl: 'partials/login.html', 
    controller: 'loginCtrl' 
  });
  $routeProvider.when('/main', { 
    templateUrl: 'partials/main.html', 
    controller: 'mainCtrl' 
  });
  $routeProvider.otherwise({ redirectTo: '/main' });
}]);

The previous snippet uses the AngularJS when() function. It accepts two parameters: the first parameter is the path of the route; the second is an object containing the information to be assigned to the current route.

So, this object can be used to store the information about the level of accessibility of each route. As example, the following snippet shows how the definition of the login route looks like:

$routeProvider.when('/login', { 
  templateUrl: 'partials/login.html',
  controller: 'loginCtrl',
  access: {
    isFree: true
  }
});

Now, everytime the user try to navigate to a new route, we have to check if he really can access that specific route. I wrote a directive for this purpose:

directives
.directive('checkUser', ['$rootScope', '$location', 'userSrv', 
  function ($r, $location, userSrv) {
    return {
      link: function (scope, elem, attrs, ctrl) {
        $r.$on('$routeChangeStart', function(e, curr, prev){
          if (!prevRoute.access.isFree && !userSrv.isLogged) {
            // reload the login route
          }
          /*
          * IMPORTANT:
          * It's not difficult to fool the previous control,
          * so it's really IMPORTANT to repeat server side
          * the same control before sending back reserved data.
          */
        });
      }
    }
  }]);

I omitted some details from the previous snippet; however the thing that is most worth to be noted is how I used the directive to register a callback in the $rootScope, for the $routeChangeStart event, fired before that the route changes.