If you’re confused about token-based authentication: this post is for you. We will cover access tokens, how they differ from session cookies (more on that in this post, and why
they make sense for single page applications (SPAs). This article is primarily written for those with a SPA that is backed by a REST API.
Thankfully, we’ve wrapped up all the best-practice decisions into some libraries you can use: Stormpath Angularjs SDK to solve your Angularjs authentication challenges, and it’s back-end pair, the Express-Stormpath.
Single page apps make a lot of sense for customer-centric applications that handle a lot of user data. SPAs are often tied to a RESTful API for a good reason: when your data makes sense, your experience makes sense. We just rebuilt our console – an Angularjs-based Single Page App – and spent a lot of time modeling out the REST API (the actual data model/structures).
With the user model mapped out, it’s much easier to build a UI for your API because the common things like list views, search boxes, sorting, etc – they just fall in place alongside your data model. Fronting a REST API with a single page app gives the assurance of sane data and the freedom to make your UI look and feel sexy. #winning
But it poses an authentication problem: how do you open up your API access in a secure way?
Since we are championing tokens, we should visit their alternative: cookie-based sessions.
Most websites use a strategy that stores a cookie in the browser. After you login this cookie contains an ID that links you to a session maintained somewhere in the server. This session knows who you are when you make a request using that cookie.
But if you’re building a SPA, it still leaves access control as a question mark: how do you let your SPA know who this user is and what they can access?
The best known solutions to authentication and authorization problems for APIs are the OAuth 2.0 spec, and tangentially the JWT specification, which are fairly dense. Cliff’s Notes Time! Here’s what you need to know:
- Json Web Tokens (JWTs) are a great authentication mechanism. They give you a structured way to declare who a user is and what they can access. They can be encrypted and signed for verification.
- The notion of scopes is powerful and yet incredibly simple: you have tons of freedom to design your own access control language.
- Refresh tokens are horribly confusing and don’t really solve any security problems if you need high security. We’ll elaborate on this later.
If you encounter a token in the wild, it looks like this:
This is a Base64 encoded string. If you break it apart you’ll actually find three separate sections:
What you see is a header which describes the token, a payload which contains the juicy bits, and a signature hash that can be used to verify the integrity of the token (if you have the secret key that was used to sign it).
We’re going to magically decode the middle part, the payload, and we get this nice JSON object:
"name": "Robert Token Man",
"scope": "self groups/admins",
This is the payload of your token, technically called the JWS Payload. It allows you to know the following:
- Who this person is and the URI to their user resource (sub)
- What this person can access with this token (scope)
- When the token expires. Your API should be using this when it verifies the token.
These declarations are called claims and they are a set of assertions that can be used to “know” things about the subject. Because the token is signed with a secret key you can verify it’s signature and implictly trust what is claimed. This provides some interesting optimizations for your backend architecture, but there are some tradeoffs and we discuss them in a later section.
Tokens are given to your users after they present some hard credentials, typically a username and password but they could also provide API keys or even tokens from another service. Stormpath’s API Key Authentication Feature is an example of this. The idea is that you present your hard credentials once and then you get a token that you use in place of the hard credentials.
The JSON Web Token (JWT) specification is gaining traction quickly. It provides structure and security, but with the flexibility to modify it for your application. We highly recommend it and have a much longer article on them: Use JWT the Right Way!
SPAs tend to have many faces: the logged in view, the logged out view, or the restricted view. It’s all about access control. Your users are all getting the same app but they may not have the same levels of access. You’ll find yourself building access control logic for your front end and your back end.
Because tokens contain all this information, they are very portable: they can be used by your UI and your backend to make decisions. You can share them with partner services as a means of building Single Sign On services that delegate users to the correct application.
There are some details that matter if you want to achieve this nirvana while maintaing security in the browser. We’ll get to that in the “Tokens Love Cookies” section.
If your tokens get stolen, all bets are off. Since we’re talking about SPAs we’re talking about web browsers, and we all know how many holes there are when it comes to securing data in a web brower.
But these problems have solutions, and this is what you must do:
- Sign your tokens with a strong key that is available ONLY to the authentication service. Every time you use a token to authenticate a user, your server MUST verify that the token was signed with your secret key.
You should encrypt your tokens if you have to put sensitive, non-opaque information in them. This is called JSON Web Encryption or JWE
You should NEVER transmit tokens over a non-HTTPS connection. Man in the middle attacks are real.
But there is one catch: you have to decide how long your tokens are valid for and how you will revoke them if you need to.
The stateless benefit of simply checking the signature is great, but it does come with a problem: it means that the access token is essentially valid forever. How do you revoke the access token if you need to?
Your next level of defense is token expiration. You should set this to a value that makes sense for your application. If you’re a bank, that might be 15 minutes. If you are a social or mobile app, you might want the token to be valid forever. But what if you do need to take it away at some point?
The OAuth solution to this problem is a two-token approach, where a short-lived access token with a longer-lived refresh token is used to get more access tokens. Im my opinion, the two-token system is a very convoluted solution that feels like it was trying to address architecture optimizations and not to make security easy. For more detail I really suggest this stack overflow answer.
The real answer to token revokation is that you need to check more than just the signature and the expiration.
In the Stormpath product we provide a “status” field on all our account objects. This lets you verify that the subject of the token (the account) is not disabled.
Another option is to maintain a record of all the tokens you give out. You add a unique value to the
jti field of the token and then retain that in your database, in such a way that they are linked to the users they were granted to. You can then add a layer in your system which allows you to declare a token invalid, and you check all incoming tokens against this blacklist.
Yes, with that last suggestion we have lost the holy grail of a stateless authentication check – but sometimes security requires tradeoffs.
I see a lot of discussions where cookies are pitted against access tokens. While we’ve all been burned by systems that store a session ID in a cookie, and that cookie is not secured and thus gets stolen. That sucks, but its not a reason to use tokens. Its a reason to avoid non-secure, non-https cookies.
Storing access tokens in HTTPS-only cookies is the best thing you can do. Never store access tokens in local storage, that storage area is very vulnerable to XSS attacks. Storing them in secure cookies achieves the following:
You don’t transmit the token over non-HTTPS connections, which are prone to man-in-the-middle attacks.
But, as always, there are tradeoffs – and there are two we care about:
The security tradeoff is that secure cookies are still vulnerable to CSRF Attacks and you need to implement a CSRF token strategy to combat this issue. This is quite trivial and our SDKs do this for you out of the box.
The application tradeoff is the loss of visibility into the token. All that great information about the subject and the claims is now hidden from your single page app. There are two ways to work around this:
Use the token to fetch the data your SPA needs
This is my preferred approach. You create a token that gives access to an endpoint such as
/user/current. This endpoint should respond with the information you need to build the SPA for the user. When your SPA bootstraps, you attempt to access this endpoint. If you get back a successful response you continue with the bootstrap. If you do not, you know that the user needs to authenticate.
Use out-of-band information
Take advantage of the response body when you are responding to a credential exchange. At the same time you write the access token to a secure cookie you can also provide some information in the response body as well. My suggestion is this: put the payload of the JWT in the response body. This allows your JS application to get that information while not exposing the signature of the token.
It does, however, mean that you should not store sensitive information in the access token payload because you’re probably going to want to cache it in the browser for future reloads of the SPA.
So many tradeoffs, so little time! My intention for this article is to give you the options for using tokens happily and securely in your single page app, so your team can make the best decisions.
Please leave your comments below and check out our new Stormpath Angular SDK and Express-Stormpath – they demonstrate how we put this into practice so it’s easy for you to take advantage of this new paradigm.
Like what you see? Follow @goStormpath to keep up with the latest releases.