JWTs offer a way to manage security concerns. In this post, learn how to generate a JWT in four steps.
Web servers need to manage security concerns by possessing the capability to grant or deny access to any entity on its resources.
This can be done using several methods, one of which is sessions, where the server uses a session id, which is attached to a cookie in a request to authenticate a user. Sessions work well with a few issues, such as the need to persist session ids to the database.
Another popular option we will explore and implement in this article is with JSON Web Token (JWT). For more information on JWTs, check out my previous post here.
Without further ado, let’s begin with generating a JSON Web Token using Node.js.
To get the best out of this article, I assume you have basic familiarity with the following:
In this section, we will learn how JWTs are created from scratch.
Note: Other third-party packages exist that abstract the complexities of creating JWTs, but we will be implementing it from scratch for the sake of clarity.
Open your terminal and insert the following:
mkdir node-jwt
cd node-jwt
touch index.js
The above code creates a directory called node-jwt
, then creates a file called index.js
inside the directory.
Generating a JWT involves doing the following:
Before we go through each step above, insert the following helper methods in your index.js
file to aid us in achieving the following:
const toBase64 = obj => {
// converts the obj to a string
const str = JSON.stringify (obj);
// returns string converted to base64
return Buffer.from(str).toString ('base64');
};
Here, we define a function that expects an object. It’s first converted to a string, then to a buffer encoded to Base64 and returned.
const replaceSpecialChars = b64string => {
// create a regex to match any of the characters =,+ or / and replace them with their // substitutes
return b64string.replace (/[=+/]/g, charToBeReplaced => {
switch (charToBeReplaced) {
case '=':
return '';
case '+':
return '-';
case '/':
return '_';
}
});
};
You might be wondering why we need another method to replace certain symbols in a Base64 string. The reason is Base64 Strings contain symbols that are misinterpreted when used in a URL.
That is, these symbols are not URL friendly when working with JWTs. Below is a table listing all of them and their replacements for more clarity.
Character | Replacement |
---|---|
“=” | “” |
“+” | “-” |
“/” | “_” |
Next, proceed by creating a sample header for the token using an object.
// suppose we have this header
const header = {
alg: 'HS256',
typ: 'JWT',
};
const b64Header = toBase64 (header);
const jwtB64Header = replaceSpecialChars(b64Header);
console.log ("the header is: ",jwtB64Header);
//OUTPUTS the header is: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Not a lot going on here. We started by creating a sample object to be the header of a JWT and encoded it to a Base64 URL.
Like the header, we create a token’s payload from an object.
// a sample payload
const payload = {
iss: 'a_random_server_name',//information about the server that issued the token
exp: 872990,// tokens expiry date in milliseconds
// information about some random user
name: 'John Bobo',
email: 'myemail@test.com',
isHuman: true,
};
// converts payload to base64
const b64Payload = toBase64 (payload);
const jwtB64Payload = replaceSpecialChars (b64Payload);
console.log ("the payload is: ",jwtB64Payload);
//OUTPUTS the payload is: eyJpc3MiOiJhX3JhbmRvbV9zZXJ2ZXJfbmFtZSIsImV4cCI6MTUwMCwibmFtZSI6IkpvaG4gQm9ibyIsImVtYWlsIjoibXllbWFpbEB0ZXN0LmNvbSIsImlzSHVtYW4iOnRydWV9
Similarly, we created and encoded an object representing the token to a Base64 URL.
The signature is what makes a JWT trustworthy. To generate one, we make use of one of Node’s core modules called crypto. The crypto module provides cryptographic functionality that includes a set of wrappers for OpenSSL’s hash, HMAC, cipher, decipher, sign and verify functions.
Add the following to your index.js file:
// bring in the crypto module
const crypto = require ('crypto');
const createSignature =(jwtB64Header,jwtB64Payload,secret)=>{
// create a HMAC(hash based message authentication code) using sha256 hashing alg
let signature = crypto.createHmac ('sha256', secret);
// use the update method to hash a string formed from our jwtB64Header a period and
//jwtB64Payload
signature.update (jwtB64Header + '.' + jwtB64Payload);
//signature needs to be converted to base64 to make it usable
signature = signature.digest ('base64');
//of course we need to clean the base64 string of URL special characters
signature = replaceSpecialChars (signature);
return signature
}
// create your secret to sign the token
const secret = 'super_secret_society';
const signature= createSignature(jwtB64Header,jwtB64Payload,secret);
console.log ("the signature is: ",signature);
//OUTPUTS the signature is Op-NH-zTetL6deCmSzyTO-f0jvVS4U7JGUG8ZryvsWE
We defined a function that creates a signature by concatenating the Base64 encoded header, a period and the payload gotten from Steps 1 and 2 above. Then, using a secret, the function generates a Hash Message Authentication Code (HMAC) using a hashing algorithm (sha256 in this case), which is then encoded to a Base64 URL and is returned.
//we now combine the results of the header,payload and signatue
const jsonWebToken = jwtB64Header + '.' + jwtB64Payload + '.' + signature;
console.log ("the JWT is :",jsonWebToken);
//OUTPUTS:"the JWT is :" eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhX3JhbmRvbV9zZXJ2ZXJfbmFtZSIsImV4cCI6MTUwMCwibmFtZSI6IkpvaG4gQm9ibyIsImVtYWlsIjoibXllbWFpbEB0ZXN0LmNvbSIsImlzSHVtYW4iOnRydWV9.Op-NH-zTetL6deCmSzyTO-f0jvVS4U7JGUG8ZryvsWE
Save the index.js file and open your terminal and execute the following:
node index.js
I hope you had fun learning about JWTs and all the interesting stuff surrounding their structure and how they are implemented. Although JWTs provide a flexible means to authenticate users, it still suffers from different types of attacks such as CSRF and XSS if not used correctly. A clear understanding of this guide gives you an edge to start using and exploring them in depth in your future projects.
Chinedu is a tech enthusiast focused on full-stack JavaScript and Infrastructure engineering.