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.
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.
We can immediately see that a JWT is made up of three parts:
- The header
- The payload
- 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.
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.
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
}