← Runnable Projects

阅读中文版本

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.

intermediatejavascriptunverified

Project

node-jwt-sign-hs256

Entry point: jwt.mjs

Install

node --version

Run

node jwt.mjs sign secret '{"sub":"1234"}'

Test

node --test jwt.test.mjs

Expected 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.