Authentication in Single Page Applications

In this article, we will see how to create URLs secured by a login and a password, in Single Page Applications. I will provide an example using node.js, ExpressJS for the server, and AngularJS for the client.

The concept remains the same for Backbone, or other front-end frameworks.

image

Different workflows

First of all, we need to know what part of the application to secure. And to know it, we need to roughly understand how a Single Page Application works (although I'm sure you are already familiar with this world).

Single Page application...

image

In this scenario, the browser will call REST web services, which are supposed to send back a JSON structure that will be used to render the HTML (in the client side). Depending on the status code returned by the server, the browser will behave differently.

So, in this scenario, we do not base the security on URLs, but on Web Services.

... versus normal Website

image

In that scenario, the workflow is a little bit different: the HTML code is generated by the server, and the browser just renders the HTML right away. If the user is not logged in, then the server will return a REDIRECT to the login page.

What does it change?

The difference is that in SPA, we have to deal with the error twice:

  • in the server side: we need to know when the user is not authenticated
  • in the client side: we need to deal with errors sent by the server (to render the login form, for instance)

In a normal website, the unauthorized error is only handled in the server side: if the user is not authorized, then we send a redirect to the client, and we display the HTML of the new URL (which is the login page).

In addition, the way a webpage is built is different: in SPA, we use web services, and in normal website, we directly render HTML pages. So, a page in a SPA can call several web services (for instance, to get the list of articles, and to get the list of tags) ; and a web service can be called from multiple URLs. It means that we need to secure web services, as well as URLs, instead of only securing URLs.

Defining the security policy

image

As an example, we can define web service for managing users:

GET /user Get list of users
POST /user Create a new user
GET /user/<id> Get detail of an user
PUT /user/<id> Update an user
DELETE /user/<id> Update an user

Defining this web service means, for example, that GET http://mydomain.com/user will return a JSON with a list of all the user ; or DELETE http://mydomain.com/user/1 will remove the user 1. Note that we haven't talked about "page" so far, but again, only basic functions provided by the server. In order to avoid people making specific query to edit our database, we obviously need to secure everything that can modify our database.

In this example, and to make it simpler, we will only secure GET /user. We can imagine that we want the list of users only accessible from the administration.

Let's code...

Server-side: authentication

We are going to code the server with node.js, ExpressJS and PassportJS (a nodejs library to deal with authentication). To make it simple, I created a single file for the entire application.

image

After installing PassportJS as explained on the website, you can define a function that is going to be used as a middleware for the secured web services. This middleware is very simple: if the user is not authenticated, we stop the execution and send a 401 status code (for Unauthorized), otherwise we continue the execution.

// Define a middleware function to be used for every secured routes
var auth = function(req, res, next){
  if (!req.isAuthenticated()) 
      res.send(401);
  else
      next();
};

Now, we define the different routes of the application. Here is the interesting part: in the /users route, we added the middleware "auth", which is (again) a function that will return 401 if the user is not authenticated (stopping the normal execution of this route).

app.get('/', routes.index);
app.get('/users', auth, user.list);

Finally, we write the routes to deal with the authentication: login, logout, and loggedin. This last function will be explained later on.

// route to test if the user is logged in or not
app.get('/loggedin', function(req, res) {
  res.send(req.isAuthenticated() ? req.user : '0');
});

// route to log in
app.post('/login', passport.authenticate('local'), function(req, res) {
  res.send(req.user);
});

// route to log out
app.post('/logout', function(req, res){
  req.logOut();
  res.send(200);
});

Client-side: AngularJS

In the client-side, we need to detect when an AJAX call returns a 401 status (which means that the user needs to authenticate), and display to the user the login form.

With AngularJS, it can easily be done by adding an interceptor to every AJAX calls:

$httpProvider.interceptors.push(function($q, $location) {
  return {
    response: function(response) {
      // do something on success
      return response;
    },
    responseError: function(response) {
      if (response.status === 401)
        $location.url('/login');
      return $q.reject(response);
    }
  };
});

This small code insures that all unauthorized requests are handled. Thus, the administration pages calling secured web services are covered ; however, there are two problems:

  • There is a flickering feeling when going to a secured page, because Angular will load the page, start the AJAX call, and afterwards, find out that the user needs to log in. As AJAX is asynchronous, it results in a clunky experience for the user
  • If a secured page does not make any AJAX request to a secured webservice, the application has no way to know that the user should log in.

Promises before loading a route

image

The idea is to create a function that will check whether the user is logged in or not by answering to a promise, so that even though we use asynchronous AJAX call to figure this out, we wait until the promise is resolved. If it is a success (the user is logged in), we continue the execution of the code, i.e. loading the page ; otherwise, we redirect the user to the login form.

var checkLoggedin = function($q, $timeout, $http, $location, $rootScope){
  // Initialize a new promise
  var deferred = $q.defer();

  // Make an AJAX call to check if the user is logged in
  $http.get('/loggedin').success(function(user){
    // Authenticated
    if (user !== '0')
      deferred.resolve();

    // Not Authenticated
    else {
      $rootScope.message = 'You need to log in.';
      deferred.reject();
      $location.url('/login');
    }
  });

  return deferred.promise;
};

To secure a URL, we simply add this new function to the configuration of the route.

$routeProvider
  .when('/', {
    templateUrl: '/views/main.html'
  })
  .when('/admin', {
    templateUrl: 'views/admin.html',
    controller: 'AdminCtrl',
    resolve: {
      loggedin: checkLoggedin
    }
  })
  .when('/login', {
    templateUrl: 'views/login.html',
    controller: 'LoginCtrl'
  })
  .otherwise({
    redirectTo: '/'
  });

Conclusion

image

In conclusion, I would give the main idea that was under this article: just define the web services that need to be secured, and secure them anyhow (PassportJS or other) by returning a HTTP code error (401 for example). If you respect this rule, your server will be secured in any case. Then, I gave a method for the client with AngularJS, but the idea is just to handle HTTP error codes.

Happy coding! ~Kev

Find the code of this article here: https://github.com/Anomen/AuthenticationAngularJS

EDIT 02/02/2015

I updated the code and the article to work with Angular 1.3.11.


Written by
August 12, 2013 3:16am
Tags: JavascriptAngularJS

Loading... Loading content...
ShareThis Copy and Paste