Jsonwebtokens or JWT as they are otherwise known is an open standard for creating a data signature that contains a payload in JSON format. A JWT is signed with a private key and/or public key and is URL-safe, this implies that a JWT doesn't have unencoded /
or ?
or &
characters. A JWT is used for authorization and lightweight authentication. The payload in a JWT will usually contain one or more claims that define the level of authorization allocated to the user.
In this article, we will see a basic implementation of JWTs to understand how they work under the hood. We will move to use a standard JWT library and practical applications JWTs in an application. Visit Netcreed to read more articles like this
Creating a JWT
You should know that a JWT is an implementation of an HMAC; Hash-Based Message Authentication Code. To create one we need the crypto module that comes baked in with Node JS, the crypto module exposes a function createHmac
function that allows us to create one. This method accepts two parameters, a hashing algorithm and a secrete, This secrete is what is used to validate the authenticity of our JWT. The secrete can be shared between a client and server if need be, but most of the time it should just be kept on the server. Let's write the function that allows us to create a signature.
import { createHmac } from 'crypto';
type Payload = Record<string, any>;
type Header = {
alg: string,
type: string
};
const createSignature = (header: string, secrete: string, payload: string) => {
return createHmac('sha256', secrete)
.update(header + '.' + payload)
.digest('base64');
}
export default function Sign (secrete: string, payload: Payload) {
const header = {
alg: "HS256",
type: "JWT"
};
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64');
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64');
const signature = createSignature(encodedHeader, secrete, encodedPayload);
return `${encodedHeader}.${encodedPayload}.${signature}`;
}
console.log(Sign("kalashin", { name: 'kinanee'}));
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoia2luYW5lZSJ9.a5koH-eUf0JXa_eX-CjriIp-Wta51ftpwqr8rRJeRF8
From the code block above, we can decipher that a JWT has three parts, the encoded header, the encoded payload, and the signature, if you are familiar with your javascript you know that the information in the header and the payload can easily be constructed. Thus it is not wise to store any sensitive information in the payload. The signature is created by calling the createHmac
function, we use the sha256
hashing algorithm, this information should also be contained in the alg
property of the header, however sha256
is not the only algorithm that can be used, JWTs support other hashing algorithms. We call update on the HMAC
object returned from the createHmac
function to add the data we want to store in it, when we are done we call digest
on it. The digest function will consume the HMAC
and return to us the calculated digest of the value passed into the update
function in an encoded string in the format passed into it, in this example we use the base64url
to ensure that our JWT is URL-Safe
.
Verifying a JWT
We have created our JWT, how can we now verify and decode the token when it is passed between the server and the client?
import { createSignature } from './sign';
export const verify = (token: string, secrete: string) => {
const parts = token.split('.')
if (parts.length !== 3) {
throw Error('Invalid token')
}
const [encodedHeader, encodedPayload, signature] = parts
const createdSignature = createSignature(encodedHeader, secrete, encodedPayload)
if (signature !== createdSignature) {
throw Error("Invalid signature")
}
return true;
}
First, we split the token string into three parts, when we created the token remember we attached the encoded headers to the encoded payload and finally to the signature using a period as a delimiter. Now we use the split method on the string to separate each part of the token, and an array with three items is returned to us. Given the same value and the same secrete, the same digest will be obtained from an HMAC object, thus we can destructure the array to obtain the values needed to reconstruct the signature then we compare the newly generated signature to the signature in the JWT and if there is a match we return true. I decided against decoding the token here, and instead, i just return true, however, let's write the script to decode the token.
Decoding JWT Payload
Most times we are interested in decoding the values stored in the token after verifying it, that is quite simple to do. We need to convert the token to an array of encoded values like we did when verifying the token, we obtain the encoded payload, then we create a base64url
buffer from it, decode the buffer to JSON then convert it back to the payload object.
export const decode = (token: string) => {
const parts = token.split('.')
if (parts.length !== 3) {
throw Error('Invalid jwt');
}
const payload = Buffer.from(parts[1].toString(), 'base64url').toString('utf8')
return JSON.parse(payload)
}
We have seen a basic implementation of JWT using NodeJS. let us now look at using a more robust solution, we will implement a JWT using the jsonwebtoken
library from npm
. This is an advanced solution for jwt implementation, allows us to set an expiry on our token, and allows us to use other hashing algorithms.
Using jsonwebtoken
To install the library run npm i jsonwebtoken
, this command installs the library from npm for us, we can then export helper functions that will sign, verify and decode the token for us.
import jwt from 'jsonwebtoken';
export const sign = function (secrete: string, data: any) {
return jwt.sign({
data,
}, secrete, { expiresIn: 60 * 60 });
}
export const verify = function (token: string, secrete: string) {
return jwt.verify(token, secrete);
}
export const decodeToken = function (token: string) {
return jwt.decode(token);
}
You will agree with me that the library implementation leaves you with a lot less to worry about, a practical application of this would be to check if a particular user is authorized to access a particular route in your application. Let's see a classical example.
import { sign } from './helper'
const payload = { type: 'ADMIN', id: 1};
const jwt = sign('1234', payload);
We have created a payload with a type property of admin and this represents an admin in your system, thus whenever a call is made to any part of your app that requires admin access, you can write a middleware that will be attached to that route. It will decode the token and verify if the user has the right access.
import { verify } from './helper'
const isAdmin = (req: Request, res: Response, next: NextFunction) => {
const token = req.headers.authorization.split(' ')[1];
if (!token) res.status(401).end('Not auth token');
const payload = verify(token, '1234');
if (payload.type !== 'ADMIN') res.status(401).end('Not authorized');
next();
}
That's it for today, to read more articles like this visit Netcreed to read more articles like this