Securing AngularJS Applications with Play

Intro

When you start migrating from classic page-reloading web applications, it can be quite confusing how to secure your JavaScript-driven app. Sure, you could keep using cookies, but cookies aren’t very secure, even more so if you allow them to be read from JavaScript.

James Ward from Typesafe wrote an excellent article about this topic, so I don’t want to repeat this here but instead encourage you to read his article first. In summary, the server generates an authentication token that our application uses to sign all requests by adding a custom HTTP header (X-AUTH-TOKEN for example).

He also shows how to implement this approach in “normal” JavaScript, jQuery and Play-Java. How is this done in AngularJS and Play-Scala? It’s actually pretty easy, let me show you how.

Signing Requests

In AngularJS we use the $http service to make AJAX-requests. $http’s provider (i.e. the module responsible for injecting the $http service), $httpProvider, offers us the possibility to define default headers. If you want to sign every request, you add them to the defaults.headers.common object. If you only want specific HTTP verbs, you can for example add the header to defaults.headers.post.

$http.post("/login", credentials).then(function(response) {
  $httpProvider.defaults.headers.common["X-AUTH-TOKEN"] = response.token;
});

This way we can set the header that is required to make authorized requests automatically on each request. Since this is such a common task and it would be cumbersome to manage this manually, AngularJS provides a way to manage this completely on its own.

The only thing required is to set a JavaScript-readable cookie named XSRF-TOKEN. Angular will pick up this cookie and add its content to each request using the customer header X-XSRF-TOKEN.

On another note, the Angular documentation mentions the cookie must be issued on the first GET request, however this is not exactly true. This is just a general guideline that stems from the fact that many applications have a separate session cookie (issued after logging in) and CRSF cookie (generated by a GET request signed with the session cookie). These applications use the session cookie to validate requests on the server, but as we learned from James’ article, this is dangerous behavior and opens up the possibility for an attacker to make authorized requests on behalf of a user. So in our case, where the Auth-Token is the sole way of making sure the request is authorized, we generate the XSRF-TOKEN after POSTing the correct credentials to /login.

It must also be stressed that this approach relies on https or anybody could be eavesdropping and easily obtain a valid token.

Server Side

On Play’s side, we have to do a couple of things.

First off, when the user logs in, we generate a token. In this example, I’ll simply generate a UUID. The token is then saved in the Cache, along with the user’s id (token -> userId), so it invalidates automatically after a given amount of time. Alternatively, you could save it in the database with a timestamp and check if it has expired on each access.

Generating the token cookie is done in Play using the withCookies method which unsurprisingly expects a play.api.mvc.Cookie. The Cookie API is pretty simple, we just need to make sure to set httpOnly to false.

When the user logs out (aka POSTing to /logout), we remove the token from the Cache and discard the cookie. When the cookie is no longer existent, Angular stops adding the X-XSRF-TOKEN header.

When we want to authorize an incoming request, we don’t want to do this in every action explicitly. So of course we do this using action composition. Our HasToken action ignores the body and only tries to extract the token header. If found, it checks if the token is in the Cache, and thus valid. If this works out, we call the action that will be “wrapped” by the HasToken action, passing it the userId we found in the Cache in the process. If any of this fails, we return a 401 status code. If you wanted a different error message in case the token is invalid, you’d use map instead of flatMap with a second getOrElse.

def HasToken[A](p: BodyParser[A] = parse.anyContent)(f: String => Long => Request[A] => Result): Action[A] =
  Action(p) { request =>
    val maybeToken = request.headers.get(AuthTokenHeader)
    maybeToken flatMap { token =>
      Cache.getAs[Long](token) map { userid =>
        f(token)(userid)(request)
      }
    } getOrElse Unauthorized
  }

Any action that requires an authorized user now composes with HasToken:

def updateItem(id: Long) = HasToken(parse.json) { currentUserId => request =>
  //...
}

Reacting to unauthorized requests

Back to the client. When we request something with a valid token, everything is fine. But what happens when the token is no longer valid? $http offers some convenience methods to make GET, PUT, POST, DELETE and HEAD requests. These not only return a promise, but an enhanced promise that allows jQuery-like method chaining with success(callback) and error(callback), and also contains the destructured response.

$http.get("/users/3")
.success(function(data, status, headers, response) {
  $scope.user = data;
})
.error(function(data, status) {
  if (status == 401)
    // go to login page
  else
    // display error
});

Not bad, but returning to the login page each time a 401 response occurs somewhere is an orthogonal concern and should be handled by an AngularJS service. And of course, Angular provides such a service. The $httpProvider, allows us to define a response interceptor. A response interceptor is essentially a function that receives every response as a promise and decides whether or not that response will be resolved or rejected.

var interceptor = function() {
  // The promise contains a response from a request
  // Our function also has to return a promise
  return function(promise) {
    return promise.then(
      function(response) { return response;}, // everything went fine
      function(response) {
        if (response.status == 401) {
          // go to login page
        }
        return $q.reject(response);
      }
    );
  };
};
$httpProvider.responseInterceptors.push(interceptor);

You can think of many more uses cases, e.g. after a 404 you could retry a request after a short delay, a 500 could display an error message, and so on. Angular 1.2 will also feature around-interceptors which make it possible to have the user login after a 401 and continue the last request without the rest of the application noticing.

Routing

When your application uses AngularJS routing, you probably don’t want to use a response interceptor for 401 codes but check authorization before a route has finished loading. When configuring your routes you can pass a third parameter called resolve, which must be an object. Each key in the object must be associated with a function. If that function returns a promise, this promise will be used by Angular to determine whether or not the route will be displayed. As a simple example, we ping the server, which then returns 200 or 401 depending on the token. The 200-range gets translated to resolve, the rest to reject.

$routeProvider.when("/users/:id", {templateUrl:"/user.html", controller:UserCtrl, resolve:{
  authorize:function($http) {
    return $http.get("/ping");
  }
 }
})

A rejected promise leads to the broadcast of the $routeChangeError event. You can subscribe to it on the $rootScope and for example reroute to the homepage or display a log in overlay.

File Uploads

How do you upload files with AJAX and JSON? One solution would be to use HTML5’s File API and XHR2, but more often than not, you have to write code that also works in IE9 and lower. In that case, you can post the file or the entire form as multipart to a hidden iFrame. ngUpload is a small and simple Angular library which does just that. It’s not wrapped in a RequireJS and it’s also not on WebJars, so we copy (or git clone) it in a folder called lib and define it as a module and declare its dependencies:

requirejs.config({
  paths: {
    "ng-upload" : "./lib/ng-upload"
  },
  shim: {
    "ng-upload" : ["angular"]
  }
});

When posting a form, we lose the ability to add our custom header. So we will have to extend our HasToken method to also accept the token as a query parameter:

val maybeToken = request.headers.get(AuthTokenHeader).orElse(request.getQueryString("xsrf"))

The back end parses this as multipart instead of JSON and either saves it in the database, cache or echoes the content as Base64.

Seeing Code is Believing

Check out my example project on GitHub. It includes the code from this article, all glued together in a small Angular + Play app.

Posted in Scala & Playframwork
4 comments on “Securing AngularJS Applications with Play
  1. Sergio Maass says:

    Nice! Thank you!

  2. listaka says:

    Thanks ! Very clear and usefull

  3. Ciaran says:

    Great tutorials, thanks.
    How would I go about changing this so it works with async actions?

  4. kayjee says:

    nice article
    any tips on how to acviev this in java

1 Pings/Trackbacks for "Securing AngularJS Applications with Play"
  1. […] el encabezado X-XSRF-TOKEN se envía el token previamente enviado por el servidor en el login. Marius Soutier tiene un artículo y código de ejemplo implementando el lado servidor en Play (el “voy a hacer algo pero alguien lo ha hecho por mi antes” ha sido una agradable […]

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Time limit is exhausted. Please reload CAPTCHA.