AngularJS web apps for Spring-based REST services security: the client side

Previous posts in this series:

The full code related to this tutorial is available on GitHub:


So far in this series of posts we have examined how to enforce a set of measures that helped us securing our REST-based server: authentication, CORS handling and CSRF prevention were built on top of the trusted and respected Spring Security framework.

We’re now going to examine how we can write our AngularJS-based web client so that it complies with all these security rules.

By the way, if you want to play around with the system it’s just a matter of running the Application class’ static main to start the server (it’s already configured to run on port 8081), and to deploy the web component on a web server such as Jetty or Tomcat, using the default 8080 port.

Calling unsecured, public services

We start with the easy part: accessing unsecured REST services!

In a typical application, some of the services are meant to be accessible by all. They don’t need to be secured. For example: if you look back at our server’ security configuration we have disabled authentication and CSRF token verification for requests starting with “/rest/open” because we wanted those to be publicly accessible.

Therefore I have defined those resources in AngularJS in the most normal way, using $resource as shown here:

There’s nothing extraordinary here; you’ve probably used $resource before. Incidentally, these cases are useful to have around if only to make sure your web client is contacting the server as it should, without having to take into account the additional “security” factor.

Log in, log out

The next step is to login so that we’re able to access the secured REST services.

By default Spring Security exposes its login entry point as a “/login” URL which expects the credentials to be submitted in a POST. But let’s not forget that we have configured the “/login” path on the server side so that it also expects a CSRF token! Login and logout are also vulnerable to CSRF attacks, remember?

So if you look at the code in login-service.js, you will see that before we post the credentials we first fire up an OPTIONS request, so we can obtain a JSESSIONID and a CSRF token back. The JSESSIONID is a temporary one, which is generated by Spring Security to store the CSRF token that it expects in our following POST to “/login“.

Now that we have a JSESSIONID and a matching CSRF token, we extract the token from the cookie bearing the name specified in $http.defaults.xsrfCookieName (which is “CSRF-TOKEN“).

We then prepare the header we’ll send along our “login” POST request with the CSRF token, because we’ve configured the server to expect the CSRF token to be submitted in the header (the cookie-to-header technique: an extra security step, remember?).

We finally post the request with the prepared header, and with the credentials in the message body:

The tricky parts in there are that:

  • the header must contain the CSRF token with the right header name, and must declare a content type of x-www-form-urlencoded (to comply to what Spring Security expects by default: a standard form submission).
  • the credentials must be sent in the body of the request in that exact format (again, to comply with Spring Security).

The logout logic is fleshed out in a similar fashion, since “/logout” is also CSRF-protected (and requires the user to be authenticated too!).

One thing worth noting regarding the logout is that I took the precaution of removing the JSESSIONID cookie at the end, just to make sure.

An important note about AngularJS app configuration

One more thing: if you take a look at the main.js file in the web component, you’ll see this:

The “withCredentials” option is used to have the browser include our credentials (most particularly our JSESSIONID cookie) to our requests. This is disabled by default, so you must enable it for our secure AJAX requests to work.

The $httpProvider defaults “xsrfCookieName” and “xsrfHeaderName” default properties respectively define the name of the cookie used by the server to send the CSRF token, and the name of the header where the server expects the token to be returned. Under those conditions, AngularJS is supposed to automatically copy the token from the cookie to the header. BUT this automatic cookie-to-header copying mechanism is not applied for cross-domain requests. Tough luck! So far I’ve been unable to find a convincing reason on why it is like this. But as a consequence of that we’ll have to copy the cookie value to the header ourselves, using those two variables merely as a convenient way to specify the cookie and header names.

Secure requests without CSRF protection

That leaves us with what I call the “secure” resources. As a reminder, let’s have a look at how we’ve configured the application security on the server:

Knowing that the requests we really want to protect from CSRF are the ones that modify the state on the server side, we really have two levels of security to take into account:

  • The requests starting with “/rest” (but not “/rest/open/“!) using GET, HEAD, OPTIONS and TRACE are protected by authentication only. Those are basically data-fetching operations, so we don’t need to CSRF-protect them.
  • All other requests starting with “/rest” (but not “/rest/open/“!), which use methods such as PUT or POST, are protected by authentication AND by CSRF protection.

Good news everyone! When you logged in, you stored a cookie with a JSESSIONID token that authenticates your session. That cookie is automatically sent back by the browser (remember that “withCredentials” option? That’s what it is for!). So basically you are already covered on that front. A GET request on a service that requires authentication is as simple as:

Easy!

Let’s move now to those requests that require a CSRF token…

Secure requests that need CSRF tokens

As we did with the “login” logic, what we need to do here is:

  1. Get a valid CSRF token as a cookie.
  2. Extract the token from the cookie and put it in the header (cookie-to-header).
  3. Submit the POST request with the header we built (which includes the CSRF token).
  4. Celebrate!

This code, extracted from main-controller.js, shows how it’s done:

We send an OPTIONS request to obtain a CSRF token and (using a custom-built Csrf service defined in csrf-service.js), we add that token in the default headers normally sent with a POST request ($http.defaults.headers.post).

Using OPTIONS to obtain tokens works because we have configured OPTIONS requests to be always permitted (no authentication needed) and to be callable without having to provide a CSRF (otherwise we would be in a catch 22 situation!).

Following that, we initialize a secure resources’ object with those headers and use it to post our request to the server.

Then we can celebrate!

How many tokens must a man ask for…

Okay, hold on: why must we obtain a token every time we want to make a CSRF-protected requests? Doesn’t Spring Security generate one CSRF token per session? Why do we keep asking for it?

The truth is… we don’t have to.

Remember: when reaching the login page, we ask a CSRF token. The server creates a temporary session and stores the token it has generated. Once we’ve authenticated, the server issues a real session ID. It will also generated a “real” CSRF token and associate it to that session. And by default it will do that once per session.

So we don’t need to keep asking for a CSRF token if CSRF tokens are generated once per session: we could ask it once, store the returned cookie, and keep using that token until the user logs out (or the session times out).

If however we decide one day to push your application’ security up one notch and generate one CSRF token per request, then we’ll need to obtain a new token every time you do a CSRF-protected request.

As it is, the tutorial’s JavaScript code is able to handle the once-per-request situations, should we want to enforce that much security. But it comes a a small cost: every POST or PUT will be preceded by an extra OPTIONS request.

Whether you are willing to accept that extra cost really depends on the project you’re working on… If you are sure you will keep the strategy of generating a CSRF token once per session, then you might want to simplify the tutorial’s logic accordingly.

That’s a wrap

With these three posts I sincerely hope I was able to offer you an introduction to a few security techniques for REST services. And that has been done by simply “bending” the well-known and venerable Spring Security framework to fit our needs. I also tried to show how these security measures are implemented on the client side, so that you can have both point of views: server and client.

Of course, the solutions exposed here will work to a certain extent but might become unsuitable for huge, massively scalable situations. If you’re off to develop the next Twitter, you might want to avoid creating sessions on the server-side and aim for pure REST stateless solutions. There are a few posts discussing the pros and cons of fully stateless REST security, such as this one.

Also there are other vulnerabilities to take into account, and OWASP should be able to help you on that level. For example, in 2013, injections (SQL, LDAP etc.) were still going strong!

Anyways… If I have forgotten something or if something is not quite clear then feel free to leave a comment. With a bit of luck, your questions or suggestions might be the starting point of my next blog post!

Until then… Cheers!

 

2 comments

  1. Luis Campos says:

    Man, I would like to thank you very much. You have no idea how much helped me with a series of posts about spring-security. I would like to talk to you about next steps as well. A strategy to dynamically define users/roles and a strategy for authorization definition for each REST service according to the roles.

    • Hello Luis,

      Thanks for the kudos!
      Your proposition about the next steps is sound and fair. As a matter of fact, I need to implement something similar for a project at work next month. So here’s the deal: I’m adding this topic at the top of my “to do” queue. Give me some time and as soon as I can I’ll add these features to the existing GitHub code and write the post that goes with it!

      Thanks for bringing it up!

Comments are closed.