JWT Storage

Token Storage

Alright, let’s say you have a sweet REST API or GraphQL API you are using for authentication and authorization. Once a client is authenticated, your server generates a JWT (JSON Web Token) and uses a secret along with a HASH algorithm to sign the JWT. It sends this to the client, and the client will send it back to your server with each request. Your server will take the JWT, use it’s secret and HASH algorithm on the JWT header and payload, and if the resulting HASH is a match your server will know the JWT is legitimate and created by your server.

Problems?

So, your JWT is not stored on the server side. REST APIs require this, the server should be stateless and not have to keep track and store each JWT. The server does not remember how many JWTs it issued, to who, or when. It can verify that it created them by looking at the JWT signature and using the secret combined with a HASH algorithm. This means no one can create a fake JWT that the server will accept without knowing the secret that only the server knows, which is stored on the server and never sent elsewhere.

So fake tokens are not a big worry, unless someone manages to break into your server (in which case you have bigger issues). What about if someone manages to steal a token? How would the server know the client sending the token was the client it had issued the token to? Well, it really does not. You can use things like IP addresses, geo-location, browser finger printing, data analysis, and others to attempt to prevent this. However, if a token is stolen it is very bad news.

A stolen token can be worse than a stolen username and password in some cases. For example, if your app has 2FA for logging on, such as a verification code sent via SMS to your phone. In this case if someone managed to steal your username and password, they could still not login and authenticate onto your account without also having access to your phone and the verification code sent to it. Once you are authenticated, the server sends you a JWT and you use that for authorization. So stealing a token bypasses the need to know the username, password, and also bypasses the 2FA.

Stop token theft!

Clearly theft of your JWT is very bad news, for both the client and the server. Since the tokens are not stored on the server (at least, they should not be!) there are only two ways to steal them:

  • In transit, such as over HTTP between the client and server
  • From the client, who is the party storing the token

Here we are focusing on storage of the token on the client side, transit token theft will be covered elsewhere.

Storage locations

So where on the client side should you store these pesky little tokens?
We have a few options

  1. Web Storage API

    • localStorage
    • sessionStorage
  2. Cookies
  3. In Memory

Spoiler alert, apparently memory is now considered the most secure place to store your tokens.

1. Web Storage API

This is localStorage and sessionStorage. The Mozilla documentation on it is here.
Neither should be used, because both local and session storage are accessible by any JavaScript you have running on your site. Third party libraries you pull in from a script tag with a link could be hacked, and the script changed to send all the local storage content (including tokens) to an evil server. XSS (Cross-Site-Scripting) is another attack where malicious JavaScript is inserted into your website, possibly also reading the contents of local storage.
In conculsion, do not use local or session storage for any sensitive information!

2. Cookies

Better than local and session storage, according to every source I could find.
if you use httpOnly to store cookies client side, JavaScript can’t access it, and it is sent automatically along with every HTTP request the client makes. There are some limitations, such as a max size of 4kb. Stil, most JWTs will not be anywhere close to that size.
No XSS worry when using cookies this way, but there is a risk of a CSRF (Cross-Site Request Forgery) attack. This is tricking the user into an unwanted action. An example would be sending a fake email with a hyperlink that does something you did not want. If you are already logged into and authenticated on a server, a CSRF could have the hyperlink in the fake email go part of the website that might transfer money, or send an email, and automatically execute the action. Say the fake email hyperlink goes to GET http://yourbank.com/transfer.do?acct=SAM&amount=10000 HTTP/1.1 well things are not looking good! You are already logged onto your banks website, and have the authorization token stored in your cookies. Now you are in your email, and when you click the fake link the cookie gets sent along with the HTTP request, since it is included in all HTTP requests by default.

3. Memory

Grab the token and store in your memory. const { token } = await response.json() storing it as variable in your JavaScript. If the page is refreshed or a tab switched, the token is lost. Storing it in memory avoids XSS attacks, and CSRF attacks. !!Except maybe it does not avoid XSS attacks, since JavaScript would have access to a global variable!! But we can persist the token so that it stays with a refresh token.

When the client first authenticates the server will send back two tokens if you want, the regular JWT which we will call the access token, and the refresh token. A refresh token is just another JWT, but with a longer experiation date than the access token. The refresh token is stored in a cookie, the access token is stored in memory.

  • Refresh token: JWT, long expiration date, stored in a cookie
  • Access token: JWT, short expiration date, stored in memory

Both the tokens are generated and sent to the client after the client authenticates. Now when the client makes a new request the the server, the refresh token will be sent as a cookie in the HTTP request, and the access token as a header in the same request. The server receives this, and checks the access token to see if it is valid. If so, it is business as normal. But what if the access token is invalid?

If the access token is invalid, such as it expired, signature does not match, or the user switched tabs and the access token was no longer in memory so it was not sent, that is when the refresh token comes into play. The refresh token persists through page refreshes and tabs being closed because it is stored in a cookie, so it still gets sent with the client request. A refresh token alone does nothing without the access token as well. A malicious attack on the cookie, CSRF, would be able to send the refresh token to the server but not the access token. The server would receive the refresh token and generate a new access token if it was valid, before sending it back to the client.

How does it protect against CSRF attacks???? Let’s say you click an evil link from your email:
<a href="http://yourbank.com/transfer.do?acct=yourname&amount=1000000">Cute Kitten!</a>

A refresh token is used to update the original token, which we will now call the access token. The access token is just your normal JWT token, which you use the espiresIn property to give an expiration date. Your access token will now stop working after a set amount of time, usually 15 minutes. When it does, the client will have to authenticate with the server again. This is a bit of a pain, entering your username and password all the time. Creaking a token to refresh your access token is the way around this, and also how we can use memory to makes things secure. Below we just generate two tokens instead of one on the server after a user logs in:

 const accessToken = jwt.sign({ username: user.username, 
role: user.role },
 accessTokenSecret, { expiresIn: '20m' });

const refreshToken = 
jwt.sign({ username: user.username, 
role: user.role }, refreshTokenSecret);