JWT Sign and Verify (HS256)
Hard · Security
A JSON Web Token (RFC 7519) is three base64url parts joined by dots: header.payload.signature. With HS256 the signature is the HMAC-SHA256 of the text header.payload using a shared secret. Servers use JWTs as login tokens, so verification must be strict: several real vulnerabilities came from libraries that trusted the alg field inside the token.
Implement with crypto.subtle:
- signJwt(payload, secret) (async): the header {"alg":"HS256","typ":"JWT"} (exactly this key order) and the payload are each turned into JSON with JSON.stringify, UTF-8 encoded and base64url encoded without padding. The signature is the HMAC-SHA256 of "<header>.<payload>", also base64url. Return "<header>.<payload>.<signature>".
- verifyJwt(token, secret, options) (async) returns { valid: true, payload } or { valid: false, reason }. options is { now, clockTolerance }: now is the current time in seconds and clockTolerance (seconds, default 0) forgives small clock differences between servers.
Verification checks, in this order, stopping at the first problem:
1. The token has exactly 3 parts, the first two are non-empty, and both decode (base64url → UTF-8 → JSON.parse) to plain objects (not arrays or null). Otherwise the reason is "malformed".
2. header.alg is exactly "HS256". Otherwise "unsupported algorithm". Never accept "none", which means "no signature at all".
3. The signature equals the one you compute (compare in constant time). Otherwise "invalid signature".
4. exp and nbf are optional, but when present they must be numbers. Otherwise "malformed".
5. The token is expired when now >= exp + clockTolerance → "expired".
6. The token is not valid yet when now < nbf - clockTolerance → "not active".
The driver runJwt(secret, ops) is already written: ["sign", payload] outputs the token, ["verify", token, options] outputs the result of verifyJwt, and ["roundtrip", payload, options] signs and then verifies.
Next: NDJSON Stream Parser