Implementing Authentication in Angular Applications

Ravi
Tweet

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.

Get your free chapter of Level Up Your Web Apps with Go

Get a free chapter of Level Up Your Web Apps with Go, plus updates and exclusive offers from SitePoint.

  • 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.

  • Arvind Kumar

    If we implement the Remember me implement vai local storage then when logout then what happen and also when user already login and also use remember me checked then logout after that he want again login that time require to again fill the form the login page or bypass the login page? how to implement that functionality??

  • Anthony Wu

    I have a little concern with session hijacking, what if I open up Chrome’s dev tools and directly insert a new key called “userInfo”? Would it be better if we could do another backend verification on init() also? (instead of checking session storage directly. )

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

      Anthony, Yes, you can do that too. Even in the current solution though the user is directed to second route, he can’t perform any action in the next page as all HTTP requests would be secured and token is checked in each of the requests.

  • MD

    Great article, thanks! One small detail for other readers who follow this tutorial: after you update authenticationSvc and add the getUserInfo() function, be sure to also update this:
    return {
    login: login
    };
    to this:
    return {
    login: login,
    getUserInfo: getUserInfo
    };
    Otherwise you’ll get error messages in your chrome developer/javascript console.

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

    Kwabena,
    In the sample, I set the object that stores user info to null. You can also make the page refresh after logging out to reset state of the JS objects, but you have to clear the value stored in session storage.

  • Corsaro

    Hi
    i m new to use this code, i try the autentication code but when i open with tool chrome see all the info user and pwd, where need to go this part for no see the info? thx

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

      Don’t store password in browser storage. I didn’t store password in the demo.
      Just store user id and any data about the user that you would require in the subsequent API calls.

  • Jinith Joseph

    Hi Ravi, that was a great article. I have the similar king of set up in my project. You have detailed how we can authenticate and store a token generated from the server. Could you also give a brief description on how we can use this authentication token in all further POST calls to the server ? Do we use them in the headers ? What are the best practices in implementing that ?

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

      Hi Jinith,
      Check the last code snippet in the article, it shows how you send the token to server using headers. But, it is not the only way. You have to send the token the way your server expects it. Till now, I saw headers in most of the examples on Node.js and ASP.NET, so header seems to be a good option.

      • Jinith Joseph

        Hi, Ravi thanks for the response. I was trying to attach the authentication headers for all the outgoing calls if they exists like :

        eisApp.run([‘$rootScope’, ‘$injector’, function ($rootScope, $injector) {
        $injector.get(“$http”).defaults.transformRequest = function (data, headersGetter) {
        if ($rootScope.oauth != null) {
        debugger;
        headersGetter()['AuthorizationToken'] = $rootScope.oauth.accessToken;
        headersGetter()['UserId'] = $rootScope.oauth.userName;
        }
        if (data) {
        return angular.toJson(data);
        }
        };
        }]);

        But on my WCF Web Service end, in Global.ascx I have few code to enable CORS :

        protected void Application_BeginRequest(object sender, EventArgs e)
        {
        HttpContext.Current.Response.AddHeader(“Access-Control-Allow-Origin”, “*”);
        if (HttpContext.Current.Request.HttpMethod == “OPTIONS”)
        {
        HttpContext.Current.Response.AddHeader(“Access-Control-Allow-Methods”, “POST, GET, OPTIONS”);

        HttpContext.Current.Response.AddHeader(“Access-Control-Allow-Headers”, “Content-Type, Accept”);
        HttpContext.Current.Response.AddHeader(“Access-Control-Max-Age”, “1728000”);
        HttpContext.Current.Response.End();
        }
        }

        When the headers are attached to the POST / GET calls this is rejecting the requests from client. CORS code works fine when there are no headers attached ( for my first call for login from the HTML 5 client ). Do you have any idea about this behaviour and how I can enable the requests with headers to go through ?

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

    Mayank,

    You shouldn’t use window in your code, as it makes the controller hard to test. You may use $window instead, you can inject it into the controller and use it. Otherwise, you can also use $location to navigate to a different page.

    • Mayank Sharma

      Ya Ravi, I know window.location is not good approach and I tries directly $location.path(“testpage”) but it was not redirecting.Basically, my aim is to firstly authenticate through hard coded value of name & password and then only it should redirect.In javascript i know but in this i was not getting.So please help me out,i tried with ui route also but its redirecting not authenticating.

  • http://thecriticalrant.com kofi

    Hi Ravi, where do you add the init function? in your app.js?

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

      It is added inside the authenticationSvc service. We are making this service responsible for holding the user’s information.

  • vijay kumar

    Hi Ravi,

    Great article, it’s working fine but i have doubt, when i refreshing the page session is expired. How to resolve the issue.

    Thanks,
    Vijay ch.

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

      Vijay,

      I am using sessionStorage to save the JWT and fetching it on every refresh to avoid accidental session expired notices. You may have missed some code to communicate with the sessionStorage and that may be causing expiry of session on refresh.

      • vijay kumar

        Thanks for your reply. In your code where app.js file included.

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

          It is in the home folder. You have to run the app using the command “node app”, and make sure to have Node.js installed on your system.

  • http://arkadefr.github.io/ aRkadeFR

    What’s the point of “getUserInfo” ?
    thanks for the article

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

      It returns the user info. The user info is read from session storage and stored in a variable when the application starts.

      • http://arkadefr.github.io/ aRkadeFR

        But what’s the point of this compared to directly giving the userInfo variable of the service? The userInfo is read from session storage *only* in the init, then the sessionStorage and userInfo are two distincts variables.

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

          The value is stored to avoid accessing session storage everytime. If you want to compare directly with session storage, you can do that.

          • http://arkadefr.github.io/ aRkadeFR

            That’s not my point. Why do you have a getUserInfo method to get the userInfo ? Why not directly getting userInfo from this service? Are the watch good with getting the property through a method (getter/setter) ?

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

            If you expose the userInfo object directly and if someone sets the value to something, then your code won’t work as expected. I wrote the getter method to avoid setting a value to the object. An alternate to this is, defining a get only property on the service object using Object.defineProperty.

            I hope that answers your question.

  • sree mohan

    how can i do this from controller?i am using local storage if user id is null route should be redirect to login page

  • Shivam

    Really helpful article sir. Thanks for sharing this with us. I have one query. Now, that I know how to handle page refreshes, I want to know how to handle “open in different tab” or “duplicate tab”. As of now, I redirect to login page. Any method(s)? For example, is there a way to remember page, or do i need to remember the login ids and re-fire all getData() requests? I have no clue. Help is requested. :)