JSON Web Token (JWT) — The right way of implementing, with Node.js

Siddhartha Chowdhury
8 min readMay 1, 2018

--

Hello guys, not so long ago the concept of JSON Web Token was introduced and it became popular very fast and loved by many developers for ease of use, scalability, and efficiency. I remember when I started implementing JWT in practice for authentication, I started with the easiest way of implementing it, with “sssshhhh” as the secret for signing a JWT. So through this article, I want to demonstrate the key aspects of implementing JWT and help in understanding the good practices for those who want to know — How to implement JSON Web Token (JWT) and have it production-ready.

What is JWT? — I believe the official JWT introduction is the most helpful resource for it.

JWT at work

So say there is an application for users to read and write articles. A user after registration wants to log in to the app. Once logged in he — updates his profile, uploads his avatar, reads few articles, writes one article and then logs out.

Technically what happened is —

After registration when the user makes a login request
1. The server verifies if the user is legit and responds with a token (JWT) containing the identity of the user.
2. The token in response is stored locally on the client system, and the user is allowed inside the application.
3. When the user makes changes to his profile, his profile [data + token] is sent to the server.
4. The server first checks if the request contains the token (responds with an error if not passed). The token is then verified, once done then the profile data from the payload is checked and respective changes are made to the database.
5. It's same for all the other actions made by the user.
6. When the user “logs out” the identification token is destroyed from the local.
The story ends here.

So let's start our exercise with a script for ease of executing and testing, later when we are confident, we will create a module that can be used in actual scenarios.
Let's begin!

Creating a JWT script

There are many npm packages out there with different flavors, we will be using jsonwebtoken npm package by Auth0. So let's start with setting up JWT using Node.js:

First thing first — Gather all ingredients before starting to cook.

'use strict';const fs   = require('fs');
const jwt = require('jsonwebtoken');

Those are the two packages we will be using for this setup. In order to create a JSON web token, we will need — three things
1. Payload
2. Secret (Private key)
3. Signing options

We will create a dummy payload, but for Secret we need to create aprivate-public key pair. There are many ways of creating keys, the quickest one would be to use an online RSA key generator. There are many options available online, I prefer either one of those
1. csfieldguide
2. travistidwell

Source: http://travistidwell.com/jsencrypt/demo/

Note the “key size” — 512 bit, there are other options too like 1024 bit, 2048 bit, 4096 bit. No doubt longer key lengths are better, but you should know that — with every doubling of the RSA key length, decryption gets at least 6 times slower. Also, it’s not quite easy to make a brute force search on a 256-bit key (but possible).

Source: http://www.javamex.com/tutorials/cryptography/rsa_key_length.shtml

So all though 4096 bit, 2048 bit, 1024 bit, or even 512 bit looks really strong, but they are too slow to use in our case. Imagine you are using 2048 bit key as a secret for our JWT, which will be decoded every time a request is sent to maintain the user session.

My chrome dies every time when I try to generate a 2048 bit key.
Anyways let's come back to our exercise, if you see the generated Private Key and Public Key carefully —
They have headers and footers like Private Key starts with
— — -BEGIN RSA PRIVATE KEY — — -
and ends with
— — -END RSA PRIVATE KEY — — -
Don’t miss those lines while copying, they are important. Save the generated Private Key as private.key and the Public Key as public.key and read them in our exercise module.

// PAYLOAD
var payload = {
data1: "Data 1",
data2: "Data 2",
data3: "Data 3",
data4: "Data 4",
};
// PRIVATE and PUBLIC key
var privateKEY = fs.readFileSync('./private.key', 'utf8');
var publicKEY = fs.readFileSync('./public.key', 'utf8');
var i = 'Mysoft corp'; // Issuer
var s = 'some@user.com'; // Subject
var a = 'http://mysoftcorp.in'; // Audience
// SIGNING OPTIONS
var signOptions = {
issuer: i,
subject: s,
audience: a,
expiresIn: "12h",
algorithm: "RS256"
};

As you can see our payload is a dummy. Always remember — keep your payload as small as possible in size, because:
1. You gonna have to pass it on each request, which will slow down your app
2. Information is sensitive, even though JWT is encoded, yet it resides in an unreliable client system.

Use utf8 character encoding while reading the private.key and private.key to get a string as content instead of byte array. There are many options available as signOption. To make the JWT efficient we will be using only the following:

  1. issuer — Software organization that issues the token.
  2. subject — Intended user of the token.
  3. audience — Basically identity of the intended recipient of the token.
  4. expiresIn — Expiration time after which the token will be invalid.
  5. algorithm — Encryption algorithm to be used to protect the token.

Now that all the requirements are in place, let's get the JWT

var token = jwt.sign(payload, privateKEY, signOptions);console.log("Token - " + token)

OUTPUT — Token in 3 parts (header, payload, and signature)

Token - eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImRhdGEyIjoiRGF0YSAyIiwiZGF0YTMiOiJEYXRhIDMiLCJkYXRhNCI6IkRhdGEgNCIsImlhdCI6MTUyNTE5MzM3NywiZXhwIjoxNTI1MjM2NTc3LCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.ID2fn6t0tcoXeTgkG2AivnG1skctbCAyY8M1ZF38kFvUJozRWSbdVc7FLwot-bwV8k1imV8o0fqdv5sVY0Yzmg

Our token is generated! This token should be passed client-side to store and then further use/add in each and every request (preferably in the header) as Token of authorization and identity.

To verify the sent token from the client, we need to do this:

var verifyOptions = {
issuer: i,
subject: s,
audience: a,
expiresIn: "12h",
algorithm: ["RS256"]
};
var legit = jwt.verify(token, publicKEY, verifyOptions);console.log("\nJWT verification result: " + JSON.stringify(legit));

Unlike signOption that we used while signing new token , we will use verifyOptions to verify the shared token by the client. The only difference is, here the algorithm is Array [“RS256”].

Note: We used private.key to sign JWT and public.key to verify it

OUTPUT — as decoded content of the JWT.

JWT verification result: {
"data1": "Data 1",
"data2": "Data 2",
"data3": "Data 3",
"data4": "Data 4",
"iat": 1525193377, // Time when the token was issued
"exp": 1525236577, // Time when it will expire
"aud": "http://mysoftcorp.in",
"iss": "Mysoft corp",
"sub": "some@user.com"
}

Our complete exercise script so far

Please note: Whatever we have done so far, is just to bring in all the components together at one place and execute them as a single script.

'use strict';const fs = require('fs');
const jwt = require('jsonwebtoken');
var privateKEY = fs.readFileSync('./private.key', 'utf8');
var publicKEY = fs.readFileSync('./public.key', 'utf8');
/*
==================== JWT Signing =====================
*/
var payload = {
data1: "Data 1",
data2: "Data 2",
data3: "Data 3",
data4: "Data 4",
};
var i = 'Mysoft corp';
var s = 'some@user.com';
var a = 'http://mysoftcorp.in';
var signOptions = {
issuer: i,
subject: s,
audience: a,
expiresIn: "12h",
algorithm: "RS256" // RSASSA [ "RS256", "RS384", "RS512" ]
};
var token = jwt.sign(payload, privateKEY, signOptions);
console.log("Token :" + token);
/*
==================== JWT Verify =====================
*/
var verifyOptions = {
issuer: i,
subject: s,
audience: a,
expiresIn: "12h",
algorithm: ["RS256"]
};
var legit = jwt.verify(token, publicKEY, verifyOptions);
console.log("\nJWT verification result: " + JSON.stringify(legit));

This will print all the components we have done so far in one shot. But in a real application, you need to create a module, divide those into functions and execute them one at a time whenever necessary.

Creating the actual JWT service module

const fs   = require('fs');
const jwt = require('jsonwebtoken');

// use 'utf8' to get string instead of byte array (512 bit key)
var privateKEY = fs.readFileSync('./private.key', 'utf8');
var publicKEY = fs.readFileSync('./public.key', 'utf8');
module.exports = {
sign: (payload, $Options) => {
/*
sOptions = {
issuer: "Authorizaxtion/Resource/This server",
subject: "
iam@user.me",
audience: "Client_Identity" // this should be provided by client
}
*/
// Token signing options
var signOptions = {
issuer: $Options.issuer,
subject: $Options.subject,
audience: $Options.audience,
expiresIn: "30d", // 30 days validity
algorithm: "RS256"
};
return jwt.sign(payload, privateKEY, signOptions);
},
verify: (token, $Option) => {
/*
vOption = {
issuer: "Authorization/Resource/This server",
subject: "iam@user.me",
audience: "Client_Identity" // this should be provided by client
}
*/
var verifyOptions = {
issuer: $Option.issuer,
subject: $Option.subject,
audience: $Option.audience,
expiresIn: "30d",
algorithm: ["RS256"]
};
try{
return jwt.verify(token, publicKEY, verifyOptions);
}catch (err){
return false;
}
},
decode: (token) => {
return jwt.decode(token, {complete: true});
//returns null if token is invalid
}
}

Added all the important notes as comments in the module. One last thing I would like to elaborate (from the above module) is the $Options

So when a user makes a login request, say he's passing his email and password. Along with email and password, the client must pass a client identity ( [payload + clientID] ), for the server to know for whom the token is to be signed.
Later for subsequent API requests with [payload + token + clientID], the server, while verifying the token will also check if the client claims to be the one this token was issued for. This clientID is usually the “audience” in the $Options .

For example — If a JWT was issued for audience — “http://abc.in”. But the client app tries to use the JWT from “http://xyz.in”, then the server should throw 403 Forbidden error as the audience identification fails to match.

This article was definitely not a short one, it was like “Getting started with JWT”. There is still a lot to be explained that can be done, like — protecting the key files, protecting the token on the client-side from hijacking, and a couple of other things in my mind. But not here, I will keep this topic for another fresh one.

I hope this article will help folks like (early) me to understand the basics of JSON Web Token. If not, at least it will definitely help to get started with JWT.

Github gist of this exercise

Other important GOOD-TO-KNOWs of jsonwebtoken :
verify() in details
decode()
— Error handling
Algorithms

--

--

Siddhartha Chowdhury
Siddhartha Chowdhury

Written by Siddhartha Chowdhury

Art, JavaScript and League of Legends

Responses (24)