Implementing Authentication in Angular Applications

Ravi Kiran

Authentication and authorization are important pieces on almost every serious application. Single Page Applications (SPAs) are no exception. The application may not expose all of its data and functionality to just any user. Users may have to authenticate themselves to see certain portions of the application, or to perform certain action on the application. To identify the user in the application, we need get the user logged in.

There is a difference in the way user management is implemented in traditional server-driven applications and Single Page Applications. The only way in which an SPA can interact with its server components is through AJAX. This is true even for logging in and logging out.

The server responsible for identifying the user has to expose an authentication endpoint. The SPA will send the credentials entered by the user to this endpoint to for verification. In a typical token based authentication system, the service may respond with an access token or with an object containing the name and role of the logged in user after validating the credentials. The client has to use this access token in all secured API requests made to the server.

As the access token will be used multiple times, it is better to store it on the client side. In Angular, we can store the value in a service or a value as they are singleton objects on the client side. But, if the user refreshes the page, the value in the service or value would be lost. In such case, it is better to store the token using one of the persistence mechanisms offered by the browser; preferably sessionStorage, as it is cleared once the browser is closed.

Implementing Login

Let’s have a look at some code now. Assume that we have all server side logic implemented and the service exposes a REST endpoint at /api/login to check login credentials and return an access token. Let’s write a simple service that performs the login action by hitting the authentication endpoint. We will add more functionality to this service later:

app.factory("authenticationSvc", function($http, $q, $window) {
  var userInfo;

  function login(userName, password) {
    var deferred = $q.defer();

    $http.post("/api/login", {
      userName: userName,
      password: password
    }).then(function(result) {
      userInfo = {
        accessToken: result.data.access_token,
        userName: result.data.userName
      };
      $window.sessionStorage["userInfo"] = JSON.stringify(userInfo);
      deferred.resolve(userInfo);
    }, function(error) {
      deferred.reject(error);
    });

    return deferred.promise;
  }

  return {
    login: login
  };
});

In actual code, you may want to re-factor the statement storing data to sessionStorage into a separate service, as this service gets multiple responsibilities if we do this. I am leaving it in the same service to keep the demo simple. This service can be consumed by a controller that handles the login functionality for the application.

Securing Routes

We may have a set of secured routes in the application. If a user is not logged in and attempts to enter one of those routes, the user should be directed to the login page. This can be achieved using the resolve block in the routing options. The following snippet gives an idea on the implementation:

$routeProvider.when("/", {
  templateUrl: "templates/home.html",
  controller: "HomeController",
  resolve: {
    auth: ["$q", "authenticationSvc", function($q, authenticationSvc) {
      var userInfo = authenticationSvc.getUserInfo();

      if (userInfo) {
        return $q.when(userInfo);
      } else {
        return $q.reject({ authenticated: false });
      }
    }]
  }
});

The resolve block can contain multiple blocks of statements that have to return promise objects on completion. Just to clarify, the name auth defined above is not defined by the framework; I defined it. You can change the name to anything based on the use case.

There can be multiple reasons to pass or reject the route. Based on the scenario, you can pass an object while resolving/rejecting the promise. We haven’t implemented the getLoggedInUser() method yet in the service. It is a simple method that returns the loggedInUser object from the service.

app.factory("authenticationSvc", function() {
  var userInfo;

  function getUserInfo() {
    return userInfo;
  }
});

The objects sent via the promise in the above snippet are broadcasted via $rootScope. If the route is resolved, the event $routeChangeSuccess is broadcast. However, if the route is failed, the event $routeChangeError is broadcast. We can listen to the $routeChangeError event and redirect the user to the login page. As the event is at $rootScope level, it is better to attach handlers to the event in a run block.

app.run(["$rootScope", "$location", function($rootScope, $location) {
  $rootScope.$on("$routeChangeSuccess", function(userInfo) {
    console.log(userInfo);
  });

  $rootScope.$on("$routeChangeError", function(event, current, previous, eventObj) {
    if (eventObj.authenticated === false) {
      $location.path("/login");
    }
  });
}]);

Handling Page Refresh

When a user hits refresh on a page, the service loses its state. We have to get the data from the browser’s session storage and assign it to the variable loggedInUser. As a factory is invoked only once, we can set this variable in an initialization function, as shown below.

function init() {
  if ($window.sessionStorage["userInfo"]) {
    userInfo = JSON.parse($window.sessionStorage["userInfo"]);
  }
}

init();

Logging Out

When a user logs out of the application, the corresponding API has to be invoked with the access token included in the request headers. Once the user is logged out, we should clear the data in sessionStorage as well. The following example contains the logout function that has to be added to the authentication service.

function logout() {
  var deferred = $q.defer();

  $http({
    method: "POST",
    url: logoutUrl,
    headers: {
      "access_token": userInfo.accessToken
    }
  }).then(function(result) {
    $window.sessionStorage["userInfo"] = null;
    userInfo = null;
    deferred.resolve(result);
  }, function(error) {
    deferred.reject(error);
  });

  return deferred.promise;
}

Conclusion

The approach for implementing authentication in Single Page Applications is quite different from that of traditional web applications. As the majority of the work is carried out on the client side, the state of the user also has to be stored somewhere in the client. It is important to remember that the state should be maintained and validated on the server as well, as a hacker can potentially steal the data stored on the client system.

The source code from this article is available for download on GitHub.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Ravi Kiran

    Mike, Sorry for catching late. You will not be able to catch the return value if you return the raw object. Because, $http.post runs asynchronously and we can’t say when it is going to end. The deferred object gets notified when the async execution is complete and it is capable of handling success/failure cases of the execution.

  • Ravi Kiran

    Maruf, Good point. Session storage is not shared across different instances even on the same browser. So, if you open a new tab, it asks for logging in. Local storage is shared, but local storage requires manual clean up on closing the last tab of the site.

  • http://sravi-kiran.blogspot.com Ravi Kiran

    Fredrik, Thanks, I got the point. You mean, we can return promise of $http and have simple return statements returning objects inside success/failure callbacks. That sounds like a good approach. Using deferred object is just more explicit way.