Securing Single Page Apps and REST Services

The move towards Single Page Apps and RESTful services open the doors to a much better way of securing web applications. Traditional web applications use browser cookies to identify a user when a request is made to the server. This approach is fundamentally flawed and causes many applications to be vulnerable to Cross-Site Request Forgery (CSRF) attacks. When used correctly, RESTful services can avoid this vulnerability altogether. Before we go into the solution, lets recap the problem.

HTTP is a stateless protocol. Make a request and get a response. Make another request and get another response. There is no correlation (i.e. “state”) between these requests. This poses a problem when you need to identify a user to the system because one request logs the user in and another request needs to tell the server who is making the request.

Web browsers have an automatic way to store some information (i.e. “state”) on the user’s machine and then add that information to every request. This is called “cookies” and they provide a convenient way to create a correlation across HTTP requests. Most web frameworks have a built-in concept called “session state” which uses a unique token for each user. That token is stored in a cookie and automatically sent to the server on each request. Now the server knows how to identify a user across requests.

This approach is simple and works great until you realize the dark truth of CSRF. Usually a user is doing something that tells the browser to make a request to server and because the cookies are sent, everything is good. But suppose the user gets an email that says “Check out these funny kittens!” with a link to a malicious website. No one can avoid seeing funny kittens, so the user clicks the link. It turns out that the funny kittens website is a malicious website which now makes some requests to an application that only uses cookies for authentication. Perhaps the malicious request is to transfer money out of your bank account. Or perhaps it posts something on a social network. These requests will be identified AS THE USER because no matter what causes the request, the browser will send the cookies. This is CSRF and many web apps are vulnerable to it.

The root of the problem is using cookies as the sole method of identifying a user since no matter how the request is initiated, the cookies which include the authentication token are always sent to the server. One way to protect against this type of attack is to force each request to contain another token which is not automatically sent. Most web frameworks provide a way to do this but they are error prone because it often requires developers to explicitly enable it and the approach doesn’t always work well with Single Page Apps.

The Way Forward

The easiest way to do authentication without risking CSRF vulnerabilities is to simply avoid using cookies to identify the user. However each request must still send a token to the server to identify the user. This requires a token to be somehow “remembered” so that each request can manually send it. Luckily Single Page Apps provide a way to keep a token in memory across requests because the page never reloads.

But what if the page does reload and the authentication token is lost because that in-memory state has been cleared? Does the user have to log back in to get a new authentication token? That would not be a very good user experience. Browsers have a few ways to store data locally across requests. The easiest is to simply use cookies. Wait… aren’t cookies the root of the problem? Cookies themselves are not the cause of CSRF vulnerabilities. It’s using the cookies on the server to validate a user that is the cause of CSRF. Just putting an authentication token into a cookie doesn’t mean it must be used as the mechanism to identify the user.

When a Single Page App loads it can read the cookies (via JavaScript), grab the authentication token, and then manually send that token on each request through a custom HTTP header. This is safe because that malicious funny kitten site does not have access to the cookies. If it did, every website would have a severe security issue.

The flow with this approach may go something like this:

  1. The user navigates in their browser to the application
  2. The server returns a basic web page and a JavaScript application
  3. The JavaScript application can’t find an authentication token in the web site’s cookies
  4. The JavaScript application displays a login form
  5. The user enters correct login credentials and then submits the form
  6. The server validates the login information and creates an authentication token for the user
  7. The server sets the authentication token in a cookie and returns it to the JavaScript application
  8. The JavaScript application makes a request for some protected data, sending the authentication token in a custom header
  9. The server validates the token and then returns the data

At step 3 if the JavaScript application does find an authentication token in a cookie then it can skip ahead to step 8.

At step 9 the server may not be able to validate the token in which case it should return a 401 (Unauthorized) response which the JavaScript application can handle by going to step 4.

There are a variety of ways to implement this approach but the real key is that the server doesn’t validate a user based on a cookie, it instead validates the user with a customer HTTP header.

This approach can be used over HTTP or HTTPS. But it is highly recommended that authentication tokens are only passed over encrypted connections which means you should probably only be using this approach over HTTPS connections. Whenever an application is not being used for local development it should automatically redirect HTTP connections to the corresponding HTTPS connection. In this setup make sure that the cookie containing the authentication token can’t be inadvertently transmitted over the HTTP connection by forcing the cookie to only be sent over HTTPS (an option which is typically available in cookie APIs).

Sample App

To better explain this approach lets walk through an example application. You can get the full source for the application from: http://github.com/jamesward/play-rest-security

This application is built using Play Framework, Java, jQuery, and CoffeeScript.

To run the application locally, download Play 2.1.1, extract the zip and optionally add the extracted directory to your system’s path. Then using a command line, navigate into the play-rest-security directory and run the following (assuming the play command is in your path):

play run

This will start the application which you can connect to in your browser at: http://localhost:9000/

You should see a login form which you can test out and once logged in, you will see the protected data and can add new data.

There are also a number of functional and unit tests for the application which validate the security of the application. You can run the tests locally by running:

play test

RESTful JSON Back-End Services

Starting with User.java you will see this is a typical database-backed entity using JPA. The User class has a property authToken which will store a single authentication token. In a real-world application you will probably want to allow a user to be logged in from multiple clients (e.g. different browsers). To enable this you could simply turn this into a list. You may also want to have some tracking on when authentication tokens are used, what IP address used them, and when they were created. The tokens could also be encrypted in the database.

The Todo.java file contains the Todo entity which stores a user’s Todos. Access to the Todo objects happen via the TodoController.java class. In this case the TodoController only has two methods, getAllTodos() and createTodo(). These methods are exposed via HTTP through the routes file. The TodoController has the @With(SecurityController.class) annotation which setups up a request interceptor so that every request made to the controller must go through the call method in the SecurityController.java class.

The call method in the SecurityController tries to find an authentication token in a custom HTTP header. If it finds a token then it tries to find a user with that token. If found the user is added to the HTTP Context (a place to store data for the duration of the request) and then the original controller method is called. Otherwise a 401 response is returned.

Both getAllTodos() and createTodo() in the TodoController use the authenticated user that was stored in the HTTP Context to either fetch the user’s todos or create a new todo.

The SecurityController class also has login and logout request handlers which are mapped to URLs in the routes file. The login method tries to locate a user by the provided username and password. If it succeeds then it creates a new authentication token for the user, then creates a cookie containing the token, and returns the token in a JSON response. The logout method uses the SecurityController interceptor to validate the user and then deletes the cookie that stores the authentication token and set’s the user’s authToken to null.

That is the RESTful back-end of the example app. Now lets explore the front-end.

CoffeeScript + jQuery Front-End UI

In the routes file you will see that requests to / are handled by returning public/index.html. This file doesn’t do much other than load jQuery and also load the index.min.js file which is compiled and minified by Play’s asset compiler. The source for that file is index.coffee and it provides the whole UI for the application. This example uses CoffeeScript because it provides a more concise and readable syntax for writing JavaScript applications.

When the page is ready the init function is called and the application attempts to find the authentication token in a cookie. If it can’t be found then a login form is displayed. If the cookie can be found then the displayTodos function is called. This function tries to fetch the user’s list of Todo objects and then display them. The request to fetch the Todo objects is a normal Ajax JSON request except that the user’s authentication token is sent in a custom HTTP header. If the server responds with a 401 error then the application calls displayLoginForm otherwise the user’s Todo objects are displayed. The createTodo function also sends the authentication token in custom HTTP header and the JSON data for the Todo within an Ajax request.

That is really all there is to the front-end UI. Most of the code in the CoffeeScript is displaying data and forms in the HTML through jQuery DOM manipulation. This DOM manipulation could also be done through one of the many client-side templating libraries.

Further Learning

The important point to remember is that using cookies for authentication opens up the possibility of CSRF attacks. Custom HTTP headers provide a more secure method of identifying users than cookies alone do. The combination of Single Page Apps and REST services provide the perfect opportunity to move away from cookie based authentication. This simple application illustrates how to implement this approach.

Learn more:

This entry was posted in Play Framework, REST, Security. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.
  • Craig Francis

    Does not work if the JavaScript fails (research “progressive enhancement”)… the typical solution to CSRF is to set a cookie that is HTTP(s) only (or store in a session, which in turn probably uses a HTTP(s) only cookie), and it simply stores a random code… this code also appears in a hidden input field in the form, so when the form is submitted the two need to match. Remote sites trying todo CSRF should not be able to find out this value, so while their form will submit along with the cookie, they won’t know the value to go in the POST value.

    • http://www.jamesward.com James Ward

      This solution certainly requires JavaScript (like all Single Page Apps).

      I explained the traditional way to deal with CSRF in the post. Was my explanation there not accurate?

      • Craig Francis

        Sorry, it didn’t really jump out at me first time… just noticed it now:

        >> One way to protect against this type of attack is to force each request to contain another token which is not automatically sent. Most web frameworks provide a way to do this but they are error prone because it often requires developers to explicitly enable it and the approach doesn’t always work well with Single Page Apps.

        Something I completely agree with, as most frameworks do the absolute minimum for this issue… to me it seems they do so just to say they have “done it”.

        But your right, its typically not enabled by default (personally I see this as a big mistake, as this kind of thing should be known about, like the mass assignment venerability found in most MVC frameworks by default).

        Anyway, it can be possible to create a single page website/app without JavaScript, for example using anchor links to scroll between “pages” (more so thinking of the static brochure-ware type websites that seem to abuse this approach).

        But I appreciate that its not always possible, and you are providing a good alternative if your going down the JS required route.

        • http://www.jamesward.com James Ward

          Cool. Thanks for the feedback and insight!

  • http://twitter.com/montesq_ Julien Montenoise

    “the funny kittens website is a malicious website which now makes some
    requests to an application that only uses cookies for authentication”

    I thought the same origin policy
    (http://en.wikipedia.org/wiki/Same_origin_policy) was in place in
    particular to prevent this kind of attack. Is it wrong?

    • http://www.jamesward.com James Ward

      The same origin policy only says that the malicious website may not read the response but it’s certainly able to make the request.

      • Łukasz Wiktor

        More precisely, the same origin policy may also prevent sending requests created in JavaScript (XMLHttpRequest). However, on the malicious site, there can be a hidden image that initiates a GET request or a hidden form that sends POST request – in these cases SOP doesn’t apply.

        • http://www.jamesward.com James Ward

          I don’t think browsers’ same origin policies prevents an XHR request. It is just reading the response which is not possible. But I might be wrong about that. Anyhow, you are right that there are many ways to exploit CSRF (images, iframes, hidden forms, etc).

          • Łukasz Wiktor

            For sure, the SOP prevents reading the response if the server doesn’t append an appropriate Access-Control-Allow-Origin header. But it may also prevent sending an actual request when you want to use methods like DELETE or PUT (all other than GET, HEAD or POST). In such case the same origin policy says that a preflight request (http://www.w3.org/TR/cors/#preflight-request) should be send and if server doesn’t respond properly, then the actual request is being blocked.

            I’m aware that this fact gives only a little fraction of security. It’s rather good to know if you want to enable cross-origin requests.

  • martijnhoekstra

    The user navigates in their browser to the application
    The server returns a basic web page and a JavaScript application
    The JavaScript application can’t find an authentication token in the web site’s cookies
    The JavaScript application displays a login form
    The user enters correct login credentials and then submits the form
    The server validates the login information and creates an authentication token for the user
    The server sets the authentication token in a cookie and returns it to the JavaScript application
    The JavaScript application makes a request for some protected data, sending the authentication token in a custom header
    The server validates the token and then returns the data

    that, or you use Basic Authentication as has been available since 1996, and make all your connections secure by only allowing https traffic as has been available in 1994.

    • Craig Francis

      James is proposing the server just respond with a token for the JavaScript to preserve, no cookies being used (which is where the issue comes in)… it does mean that the JavaScript developer needs to use their own authentication system (typically not ideal, as most people can’t think of every attack vector, and a simple HTML injection will render this as venerable as most other solutions).

      But “Basic Authentication” has its own issues as well… it is also susceptible to CSRF (but *might* stand a slightly better chance of the browser being able to identify *some* of the attacks, but I don’t believe any browser attempts todo so at the moment).

      And often the browsers native UI for this kind of login is quite bad… typically no multiple session support, no reminder of the current login, no way to logout without shutting down the browser, and difficulty with integration with 3rd party password managers (e.g. 1Password).

    • http://www.jamesward.com James Ward

      Yeah, like cookies the browser also automatically sends basic auth headers so as Craig points out, it is also prone to CSRF vulnerabilities.

      I’m not sure what you mean by “make all your connections secure by only allowing https”. Was the section of this post about using HTTPS for transport security not clear?

  • pavan bangaram

    This is Good information regarding providing security for single page apps…! than x for providing this information…!

  • Brian Repko

    I wouldn’t use a custom HTTP header – just use what is there already – “Authentication”

    • http://www.jamesward.com James Ward

      Are you referring to basic auth? Doesn’t it automatically get sent by the browser (like cookies)?

  • Suresh

    Excellant article! I just cloned the repo and tried to run the app (play run), but hitting with the following error.

    [warn] [NOT FOUND ] org.slf4j#slf4j-api;1.6.6!slf4j-api.jar (0ms)

    [warn] ==== local: tried

    [warn] /Users/sgopal1/install/play-2.1.2-RC1/repository/local/org.slf4j/slf4j-api/1.6.6/jars/slf4j-api.jar

    [warn] ::::::::::::::::::::::::::::::::::::::::::::::

    [warn] :: FAILED DOWNLOADS ::

    [warn] :: ^ see resolution messages for details ^ ::

    [warn] ::::::::::::::::::::::::::::::::::::::::::::::

    [warn] :: org.slf4j#slf4j-api;1.6.6!slf4j-api.jar

    [warn] ::::::::::::::::::::::::::::::::::::::::::::::

    sbt.ResolveException: download failed: org.slf4j#slf4j-api;1.6.6!slf4j-api.jar

    at sbt.IvyActions$.sbt$IvyActions$$resolve(IvyActions.scala:214)

    Any idea?

    • http://www.jamesward.com James Ward

      Are you running with Play 2.1.1?

      • Suresh

        no, play-2.1.2-RC1. Is it mandatory to have 2.1.1?

  • Alex Curtis

    Nice one James, would be good to get this included in the Play examples.

  • webiyo

    Similar Solution Implemented using backBoneJS, The API validates the incoming requests using Query String Parameter.

    http://stackoverflow.com/questions/14710837/keep-session-across-page-reload-in-backbone-js/14728192

  • Raymond Lau

    Thanks for the writeup! I didn’t have an idea as to how things are done authentication wise, and this helped me start.

  • Pingback: - Marius Soutier NoDev - Not Only Software Development

  • Marius Soutier

    Shamless plug – how to implement this with AngularJS and Play-Scala:

    http://www.mariussoutier.com/blog/2013/07/14/272/

    • http://www.jamesward.com James Ward

      Awesome stuff! Thanks.

  • Jerry

    Awesome article! Thanks James.
    What if a user does not touch the page for a long time, what is the scenario for the cookie to be expired?

    • http://www.jamesward.com James Ward

      Play has a default cookie expire date but it can be overwritten.

      • Jerry

        James, when the token is generated and stored in cookie, what is the best way for server side to tell that this token expires?

        • http://www.jamesward.com James Ward

          The way I’ve done this in the past is to store a last used date with the auth token. Then an old session reaper can delete the old tokens after a given amount of time.

  • Kurt Legerlotz

    Hey James, you mentioned about turning the single auth token into a list to support multiple logins across devices, etc. Just curious if you’ve got any thoughts about how to keep that list from growing indefinitely? The IP address could be used to ensure only one auth token per IP, but that still may not guarantee orphaning auth tokens on the User in the DB.

    Perhaps when users are loaded, old auth tokens could be scraped off?

    Thanks!

    • http://www.jamesward.com James Ward

      You could timestamp the auth tokens when they are used and then have a reaper which cleans up stale auth tokens. Or on login cap the max number of auth tokens a user can have and remove the oldest used ones. Think that would work for you?

      • Kurt Legerlotz

        Yep, thanks!

  • Manuel Alzola

    Hello, I’m trying to apply your article to a scala app and the problem i’m stuck with is the HttpRequest.Context, that seems not available on scala api. Do you know what would be the equivalent place to store the user for the duration of the requests?
    Thank you very much for a nice article

    • http://www.jamesward.com James Ward

      I think that people often use implicits in place of HttpRequest.Context in Scala.

      • Manuel Alzola

        I’m going to search for samples of that, thank you very much!

  • Marko

    Why not also randomize the channel by giving every rest possible rest call of the application a unique token and also randomize the request header attributes name with a high entropy random string?

    >:)

  • Ping pong

    Hi, thanks for this post. For a novice like me it’s really valuable lecture.
    One question though. You’re using Java and Play Framework for server side. Do you or any of you guys know a lightweight framework or a good materials for doing it in PHP?
    Just a longshot, but this solution of yours sounds very good and I would like to try it.
    Cheers,
    D.

    • http://www.jamesward.com James Ward

      I don’t. Sorry.

      • Pp

        Thanks anyway for a great read.

  • Joa

    Are there any disadvantages in using the localStorage instead of cookies to remember the auth token, expect from browser support?

    • http://www.jamesward.com James Ward

      That is definitely another option but will reduce the supported browsers a bit.

  • C. Daniel Sanchez R.

    There any way to mix this concept with third-party authentication? (oauth in fb, g, tw… etc).

    I looking for this and found pac4j and SecureSocial, but both have a focus on traditional MVC (multiple pages-like, and cookies session). Is any better way to do this?

    • http://www.jamesward.com James Ward

      Sure this could definitely be used with OAUTH and other auth mechanisms because they all ultimately just use a token to identify the user.

  • sanderjd

    Thanks for the great post!

    It seems like your approach trades off stronger protection against XSRF for weaker protection against the effects of XSS. The best practice for session cookies is to make them HttpOnly so that an attacker can’t steal a user’s session if they manage to access document.cookie through an XSS attack. In your solution, the cookie storing the login token can’t be HttpOnly, as the javascript application needs to read it. I think this would be the case regardless of whether you use (non-HttpOnly) cookies, sessionStorage, or localStorage.

    I like the standard HttpOnly session cookie along with the way angularjs suggests doing XSRF protection: the server sets a XSRF token in a non-HttpOnly cookie called XSRF-TOKEN, the $http service automatically sets the X-XSRF-TOKEN header to the value of the cookie on all requests, and the server checks the header for the expected value. This is detailed at http://docs.angularjs.org/api/ng.$http (search for XSRF). Note that no javascript can read the session cookie (XSS safety), and only javascript executing at the correct domain can read the XSRF-TOKEN cookie (XSRF safety). It’s important that the server not use the value of the XSRF-TOKEN *cookie* when comparing to the expected XSRF token, but rather the X-XSRF-TOKEN *header*. The cookie will always be correct, but forged requests won’t have the header.

    • Michael Ball

      Thanks for the excellent point and I too feel more comfortable with this approach. I really like the ideas presented in the post, but am concerned that the session cookie could not be marked as HttpOnly and the security implications of that.

      • sanderjd

        Cool. The pattern I described is actually really easy to do, but I made a simple little library[0] for Rails to make it work automagically with AngularJS, if that helps you!

        [0]: https://github.com/jsanders/angular_rails_csrf

        • http://www.thibaultjeandet.com/ Thibault J.

          Thanks for the article and comment this is extremely useful. So what you suggest is that in order to protect against XSS and CSRF, both the httpOnly session cookie AND the XSRF header need to be checked and validated on the server side ?

  • Pingback: Stateless authentication in Symfony 2 | darsadow

  • Daniel Martin

    Is there a reason why you chose to implement this in Java? I’m fairly new to Scala and Play. I’m trying to implement a RESTful server to support a single-page app. I know Scala and Java can co-exist. I’m just trying to decide wether to implement a similar solution in Scala or just use what you did in Java.

    • http://www.jamesward.com James Ward

      I could had done it in either but decided to use Java for this one since I figured most of my readers would be familiar with it.

  • niko

    Hi,

    The article was about CRFS but how to protect against RF (not cross site). Let’s assume that I haven’t been able to make 100% perfect xss sanitations and there’s a way to make stored xss on my page. This becomes very dangerous if it can be chained with request forgery.

    Would it be good protection like this?
    – When page first loads, it would get a token that would be read with javascript and would go into private application scope (and would not be seen anywhere else)
    – Now all the view-js that are inside the same scope would see the token and could send it with request header
    – If someone injects script in a document that script doesn’t have access to private scope because it’s ran in window scope.
    – .. but the malicious script still can access all html elements and inject stuff into forms and then trigger submit button.. and in that case we are out of luck.

    Does this make any sense? Is there a way to prevent that last case when xss script injects data into form and submits it the way normal user would? I’m thinking it’s impossible to distinguish between real user and what js does but is it really?

    • http://www.jamesward.com James Ward

      That is a good question. If someone can inject JavaScript into the page there is no way to guarantee that it will be treated differently than the “validated” JavaScript. After all, injected JavaScript can overwrite methods on Object and do whatever they want to the DOM.

    • Gili

      All CSRF protections fail in the face of XSS.

      You must protect against XSS, period.

  • Saeed Zarinfam

    Does this solution work for Android Apps and REST Services?

  • Saransh Mohapatra

    “This is safe because that malicious funny kitten site does not have access to the cookies”. Yeah it doesn’t only when the cookie is httponly. But in that case our javascript won’t be able to read the cookie. So whats your solution to storing the cookie.

    • http://www.jamesward.com James Ward

      The malicious site isn’t going to be able to read the cookies no matter what. But if you want to use httponly and have a token across requests then you can store it in local storage instead of a cookie.

      • Saransh Mohapatra

        But isn’t it true that setting httponly on session(authentication) cookies is preferred ?? N how safe is storing it local storage.

  • Saeed Zarinfam

    Why you did not use authenticator and use @With?

    • http://www.jamesward.com James Ward

      I can’t remember. There might have been a reason or maybe I just didn’t know I could at the time. If someone wants to fork my code and implement it with authenticator it would be interesting to see the differences.



  • View James Ward's profile on LinkedIn