web #authentication #jwt #node.js

Simple JWT Storage and Request Handling - Cookies are tasty

Jan 4 '19 · 5 min read · 830

How to store and send JSON Web Tokens (JWTs) from the client-side.

Intro

A

mongst other things, JWTs enable a stateless way of implementing authentication, in comparison to old-school sessions. Tutorials are great at showing you how to generate a JWT and send it to the client, but it's not obvious where to store these tokens on the client-side, and how they are sent with a request to the server. There are a few different ways to do this, but this post will explore storing them in cookies, which I believe is the simplest solution. I will also briefly mention some security issues that you must be aware of and defend against when using JWTs.

JSON Web Tokens - JWTs

If you don't know what JWTs are, or have not used them before, check out this great introduction to get up to speed before reading on. Also take a look at this post by joepie91, which breaks down what people often get wrong about JWTs - what they are, and what they are not. Finally, have a play around with this debugger using the following token with the secret key "mysecret", as an example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1YzJlNjA3NDViNDU3MTI3ZDk0NmRkODQiLCJhY2Nlc3MiOiJhdXRoIiwiaWF0IjoxNTQ2ODY2OTgzfQ.xzFR7BTZvUiA42V34J3sOI0tVh0XaGaonKxwcm1AIOs

JWT Anatomy

Looking closer at the token string, you can see that it is made up of three distinct parts separated by periods ('.'). These are called the head, payload, and signature, respectively. The head stores the algorithm used to generate the signature (e.g. HS256), the payload is where the data you want to transfer is stored (e.g. userId) and the signature is generated using said algorithm using the data and your secret key as inputs.

JWT logo over Ventruvian Man drawing

The signature is not a method of encrypting the payload, instead it is used to verify that the token originated and was generated on your servers. It is only if you have the secret key that you would be able to generate the correct signature. It is usually a good idea to change the secret key periodically and store it in an environment variable rather that as plain text in code.

The JWT can be thought of as a key to various parts of the application. The signature tells the application whether or not the JWT is a key that the application itself gave to you, or if its a forged key that the application did not give to the user and should not be accepted as a key to unlock things.

An example JWT annotated with head, payload and signature parts

Unless you encrypt the token - or parts of the token in further steps - the token is only Base64 encoded, which means it can be decoded by anyone even without knowing the secret key - just like you have seen with the example token above using the debugger page. This means that JWTs should not be used to transfer private data such as personal details.

JWTs for Authentication

Authentication is the predominant use case of JWTs. If the credentials provided by the client are correct, the subsequent key steps in the log in process are:

  1. Token generated on the server containing the user's id in the payload
  2. Token stored in a database and associated with the user to track which tokens are active and have not expired
  3. Token sent back to the client to complete the login process

To use the token to access private content, the client must send the token to the server with each request for such content.

The server will decode and verify that the token was generated and signed by the application's servers, using the secret key. Once verified, the user database is queried for a user matching the userId stored in the token payload and the token string itself. This ensures that expired tokens, which may still be verifiable if the secret key has not changed, are not accepted.

When a user logs out, the JWT is still sent to the server but this time the matching token is deleted from the database so that it can no longer be used.

Diagram showing the flow of data during the login process to generate a JWT

Client-Side JWT Storage

There are mainly two ways of storing JWTs on the client: cookies and local storage. Both methods have their pros and cons, which is summarised nicely in this post by Tom Abbott. The jist of this post is that both methods have security issues, but local storage is probably considered more secure.

Why Cookie Storage?

If local storage is considered more secure, why use cookies?

First of all, when correct security measures are taken, both methods can be just as secure as each other. The main reason for using cookies over local storage is ease of use. Cookies are automatically sent with every request to the origin domain (e.g. whenderson.net) that the cookie is registered with, which means you don't need any extra code on the client-side to send your JWT back to the server to access private content. Furthermore, cookies can be set from the server-side, which means that there is nothing extra that the client needs to do.

"Illusion appeals creativity. Authentication validates reality."
- Yash Sanjayrao Yawalker

This is in comparison to local storage, with which you need to manually set the JWT from the client-side as part of the header of an AJAX request every time you want to access private content.

Storing JWTs in Cookies using Node.js

Below is the POST /login route of this blog. It shows that, if the provided email and password match a user, a token is generated that contains the userId. The token is then stored in a cookie using res.cookie(key, value) with the key set to 'x-auth'. The key can be set to anything you want, it just means that you have to look for the JWT using this key when authenticating.

// Login request
app.post('/login', async (req, res) => {
  try {
    const body = _.pick(req.body, ['email', 'password']);
    const user = await User.findByCredentials(body.email, body.password);
    const token = await user.generateAuthToken();
    res.cookie('x-auth', token).redirect('/');
  } catch (e) {
    res.status(400).send();
  }
});