Skip to main content
Donate to support Ukraine's independence.

Dissecting JWT (JSON Web Tokens)

Authentication may be a scary and delicate topic, since dealing with it involves diving into the security details of an application. There are various ways for dealing with authentication, such as HTTP BASIC AUTHENTITCATION, OAuth 1.0, OAuth 2.0, etc. However recently I came across JWT (JSON Web Token), so here we will see how these tokens work.

Authentication vs Authorization
Authentication is the process used to verify that a specific user really is who he claims to be. Instead authorization is the process used to verify that a logged in user has the rights to access a certain protected resource.

JWT Components

There’s an interesting website that we can use to learn more about JWTs and the components they are made of: jwt.io; we will use it to debug JWTs and discover how they are made. Below there’s a screenshot taken from that website.

A JWT from the jwt.io debugger

We can immediately see that a JWT is made up of three parts:

  1. The header
  2. The payload
  3. The signature

The header contains some metadata about the JWT itself, such the method used to generate the signature (in our case HS256, a.k.a HMAC-SHA256). The payload, as the name suggests, contains whatever data we want to encode into the JWT; the more data we encode, the bigger the JWT will be. Both the header and the payload are separately encoded using base64url (RFC 4648) and visible in plaintext, they are not encrypted. This means that anyone can decode them and read them.

Important
Since anyone can decode the header and the payload, it is strongly adviced not to put anything sensitive (such as passwords) into the payload.

The signature is where things get interesting: it is created using the header, the payload and a secret string stored on the server; the whole process is called “signing the JSON Web Token”. This means that only those header, payload and secret can create a specific JWT, which then gets sent to the client after logging in. This JWT is then sent by the aforementioned client to the server to verify if he is who he really claims to be, for example to access protected resources on the server that should be available only to registered and logged in users.

Usually common practice dictates that the token is sent to the server with an HTTP Header with the request; the header key should be called Authorization and the value should always start with Bearer followed by a blankspace and the value of the token.

Verifying the JWT

The verification process aims to verify that no one tampered with the data, meaning that no one should have maliciously changed the header and the payload of the token. In order to do this, once the JWT is received on the server, a test signature is created by using the header and the payload of the JWT, together with the secret; keep in mind that in the meantime the original signature is still in the token. Now all we have to do is compare the test signature with the original signature. If they are the same, we can be sure that no alterations have been made, so we can authenticate the user.

Keep in mind that third parties do not have access to the secret, so they cannot sign the JWT.

Important
Remember to safely store the secret used to sign JWTs following security best practices.

The jsonwebtoken npm package

If you use NodeJS and npm, you can use the jsonwebtoken package in order to deal with everything related to JWT. This package also allows to set token expiration dates. More details here.

Here are some code snippets (the verifyAndProtect function can be used in Express.js):

const { promisify } = require('util');
const jwt = require('jsonwebtoken');

// code to synchronously sign the JWT
jwt.sign({ data: 'some_data_such_as_user_id' }, 'your_secret_goes_here', {
    expiresIn: '90d', //expires in 90 days
  });
exports.verifyAndProtect = async (req, res, next) => {
    let token;
    //Let's get the token from the Authorization header
    if (
        req.headers.authorization &&
        req.headers.authorization.startsWith('Bearer')
    ) {
        token = req.headers.authorization.split(' ')[1];
    }

    if (!token) {
        //It is advised to write an error middleware to handle this kind of errors in Express.js
        return next(new Error('You are not logged in! Please, log in.'));
    }
    try {
        // code to verify the token, uses a callback function
        // so we "promisify" it
        const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
    } catch (err) {
        return next(new Error('Invalid token'));
    }

    //Now we can safely access to token data.
    //REST OF YOUR CODE HERE
}

Send a like: