Session Management with JSON Web Tokens Built With Node.js Crypto Module
A basic feature of any web application is user management, i.e., how to create a sign in or user account feature? This problem boils down to session management. As the user navigates your web site how does your web server know who sent the request? Authentication is another word for it. The web server needs to know — who are you and are you really who you say you are? Two solutions to this problem have evolved — server side sessions or client side tokens. The former requires you to store session information server-side in a state table, the latter requires the browser to store the session information in an HTTP cookie.
Sessions
This solution works by sending the client / browser a code to store in local storage or a cookie. This code is created by the server in response to a challenge such as username / password or email link. The server has to keep these codes in a database. If the incoming request has a code then look in the database to see who this is based on the code.
JSON Web Tokens
This solution works by sending the client / browser a long string separated into 3 parts by periods, e.g. xxxxxxx.yyyyyyyy.zzzzzzzzz
. The format of this string is specified in the Internet Engineering Task Force’s RFC 7519. The idea is that the usable body of the token, the yyyyyyyy
part, that contains the users identification is trusted because it is signed by the server. Signed means that the zzzzzzzzzz
part, the signature, can only be verified by the server. It’s a hash, meaning that it’s a scrambled key that can only be unscrambled with the servers key. A mathematical function where the probability of generating that mish mashed signature without the key is practically impossible. In this case we will use RSA asymmetric keys.
The key point here is that the session information, or authentication information, is stored on the browser. No need to keep track of session codes in a database server side. Each request coming in to the server includes the JWT which is read and at that point the server knows who this is. Nice.
Implementation of JSON Web Token Using Node.js Crypto Module
Below illustrates how to both issue and verify JWT’s.
jwt.mjs
const {createSign,createVerify} = await import('node:crypto');
import { Buffer } from 'node:buffer';
const headerObject = {
alg: 'RS256',
typ: 'JWT',
};
const headerString = JSON.stringify(headerObject);
const encodedHeader = Buffer.from(headerString).toString('base64url');
function issue(privateKey,id,email){
let oneMonth = 2629800000;
let exp = Date.now() + oneMonth;
let payloadObject = {
"iss": 'my_server_name',//information about the server that issued the token
"exp": exp,
"id": id //put other stuff in here too to suit your needs
};
let payloadString = JSON.stringify(payloadObject);
let encodedPayload = Buffer.from(payloadString).toString('base64url');
const sign = createSign('SHA256');
sign.write(encodedHeader + '.' + encodedPayload);
sign.end();
let signature = sign.sign(privateKey, 'base64url');
let jsonWebToken = encodedHeader + '.' + encodedPayload + '.' + signature;
return jsonWebToken;
}
function verify(publicKey,jwt){
let jwtParts = jwt.split('.');
let jwtHeader = jwtParts[0];
let jwtPayload = jwtParts[1];
let jwtSignature = jwtParts[2];
const verify = createVerify('SHA256');
verify.write(jwtHeader + '.' + jwtPayload);
verify.end();
let obj = {};
obj.valid = verify.verify(publicKey, jwtSignature, 'base64url');
obj.payload = {};
try {
jwtPayload = JSON.parse(Buffer.from(jwtPayload, 'base64url').toString('utf-8'));
obj.payload.id = jwtPayload.id;
obj.payload.email = jwtPayload.email;
} finally {
return obj;
}
}
export default {issue,verify};
In terms of handling HTTP requests, you simply read the cookie from the request header and process it. Now you can handle the request as you wish, knowing who it came from. Issue the JWT after challenging the client, e.g. username/password or email link auth.
server.mjs
import jwt from './jwt.mjs';
const { privateKey, publicKey } = getKeys(); //before we start server we need RSA keys
const SERVER = http.createServer(async function(request, response) {
let statusCode = 200;
let user = undefined;
if(request.headers.cookie){
let cookies = parseCookies(request.headers.cookie);
let token = cookies.token;
let jot = jwt.verify(publicKey,token);
user = jot.payload.id;
}
console.log("request fired with user = " + user);
...
function getKeys() {
if (fs.existsSync(PUBLIC_KEY_FILENAME)) { //public key file found on disk
let privateKey = fs.readFileSync(PRIVATE_KEY_FILENAME, 'utf8');
let publicKey = fs.readFileSync(PUBLIC_KEY_FILENAME, 'utf8');
return {privateKey, publicKey};
} else {
let { privateKey, publicKey } = generateKeyPairSync("rsa", { //public key file NOT found
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
}
});
fs.writeFileSync("public.pem", publicKey);
fs.writeFileSync("private.pem", privateKey);
return {privateKey, publicKey};
}
}
Conclusion
There are many libraries out there that handle the implementation of JWT’s. Now you know how to implement them using only Node.js.