JWT HS256 Sign and Verify CLI
A Node CLI that signs a JSON payload into a JWT with HS256 and verifies a token, using only the built-in crypto module and constant-time comparison.
Project
node-jwt-sign-hs256
Entry point: jwt.mjs
Install
node --versionRun
node jwt.mjs sign secret '{"sub":"1234"}'Test
node --test jwt.test.mjsExpected output
Prints a three-part JWT whose signature verifies with the same secret; verify prints true for a valid token and false for a tampered one.Dependencies
- node >=20
What it does
Base64url-encodes the header and payload, signs the signing input with HMAC-SHA256, and appends the signature. Verification recomputes the signature and compares it in constant time so timing does not leak how much of the signature matched.
import { createHmac, timingSafeEqual } from 'node:crypto';
const b64url = (buf) => Buffer.from(buf).toString('base64url');
export function sign(payload, secret) {
const header = b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
const body = b64url(JSON.stringify(payload));
const input = header + '.' + body;
const sig = createHmac('sha256', secret).update(input).digest('base64url');
return input + '.' + sig;
}
export function verify(token, secret) {
const parts = token.split('.');
if (parts.length !== 3) return false;
const input = parts[0] + '.' + parts[1];
const expected = createHmac('sha256', secret).update(input).digest();
const provided = Buffer.from(parts[2], 'base64url');
return provided.length === expected.length && timingSafeEqual(provided, expected);
}
const [mode, secret, arg] = process.argv.slice(2);
console.log(mode === 'verify' ? verify(arg, secret) : sign(JSON.parse(arg), secret));
Run and verify
node jwt.mjs sign secret '{"sub":"1234"}'
Run the test command to check the implementation: node --test jwt.test.mjs.
Expected Output
The token has three base64url segments separated by dots. Verifying the token against the same secret and payload returns:
true
Limitations
Implements HS256 only; it does not validate exp, iss, or aud claims, so callers must check expiration themselves before trusting a payload.