shjwt
Version:
The setHacked JWT module.
106 lines (89 loc) • 3.61 kB
JavaScript
/**
* @fileoverview shJWT module. This is a library that provides methods to encode
* and decode data using JSON Web Tokens(JWT) and HMAC with SHA256 for signing.
* @module shjwt
*/
const crypto = require('crypto');
/**
* Base64URL encode a string according to the JWT standard.
* @param {Buffer} buffer - The data to encode.
* @return {string} Base64URL encoded string.
*/
const base64UrlEncode = (buffer) => {
return buffer.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
};
/**
* Base64URL decode a string according to the JWT standard.
* @param {string} str - The string to decode.
* @return {Buffer} Decoded Buffer.
*/
const base64UrlDecode = (str) => {
const padding = 4 - (str.length % 4);
const base64 = str.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat(padding);
return Buffer.from(base64, 'base64');
};
/**
* Encode the payload into a JWT format.
* @param {Object} payload - The payload that will be encoded.
* @param {string} secret - The secret key for generating the HMAC.
* @return {Promise<string>} A Promise that resolves with the JWT token.
* @throws Will throw an error if an issue occurs during encoding.
*/
exports.encode = async (payload, secret) => {
try {
if (!secret || secret.length < 32) {
throw new Error('Secret key must be at least 32 characters long');
}
const header = { typ: 'JWT', alg: 'HS256' };
payload.iat = Math.floor(Date.now() / 1000); // Issued at timestamp
const base64Header = base64UrlEncode(Buffer.from(JSON.stringify(header)));
const base64Payload = base64UrlEncode(Buffer.from(JSON.stringify(payload)));
const signature = crypto.createHmac('sha256', secret)
.update(`${base64Header}.${base64Payload}`)
.digest();
const base64Signature = base64UrlEncode(signature);
return `${base64Header}.${base64Payload}.${base64Signature}`;
} catch (error) {
throw new Error(`Error encoding token: ${error.message}`);
}
};
/**
* Decode the JWT into the payload.
* @param {string} jwt - The JWT to be decoded.
* @param {string} secret - The secret key for verifying the HMAC.
* @return {Promise<Object>} A promise that resolves with the decoded payload.
* @throws Will throw an error if the JWT is not well formatted or if the
* verification failed.
*/
exports.decode = async (jwt, secret) => {
try {
if (!secret || secret.length < 32) {
throw new Error('Secret key must be at least 32 characters long');
}
const [base64Header, base64Payload, base64Signature] = jwt.split('.');
if (!base64Header || !base64Payload || !base64Signature) {
throw new Error('Invalid token format');
}
const header = JSON.parse(base64UrlDecode(base64Header).toString('utf8'));
if (header.alg !== 'HS256') {
throw new Error('Unsupported algorithm');
}
const payload = JSON.parse(base64UrlDecode(base64Payload).toString('utf8'));
const expectedSignature = crypto.createHmac('sha256', secret)
.update(`${base64Header}.${base64Payload}`)
.digest();
if (!crypto.timingSafeEqual(Buffer.from(base64UrlDecode(base64Signature)), expectedSignature)) {
throw new Error('Signature verification failed');
}
// Expiration check
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
throw new Error('Token has expired');
}
return payload;
} catch (error) {
throw new Error(`Invalid token: ${error.message}`);
}
};