iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

JWT / JWS

A JWT (JSON Web Token) is three base64-url-encoded parts: header, payload, signature. Used for authentication, API access, password resets. Easy to use badly — pin the algorithm, validate everything.

Sign, verify, common mistakes

EXAMPLE
import jwt from 'jsonwebtoken';
import crypto from 'node:crypto';

const SECRET = process.env.JWT_SECRET;     // at least 32 random bytes

// 1) SIGN — pin algorithm + expiry + audience + issuer
function signSession(userId) {
    return jwt.sign(
        { sub: String(userId) },           // payload claims
        SECRET,
        {
            algorithm: 'HS256',
            expiresIn: '15m',
            issuer:    'myapp',
            audience:  'myapp.web',
            jwtid:     crypto.randomUUID(),
        },
    );
}

// 2) VERIFY — REQUIRE the algorithm, audience, issuer
function verifySession(token) {
    return jwt.verify(token, SECRET, {
        algorithms: ['HS256'],             // CRITICAL — pin
        issuer:     'myapp',
        audience:   'myapp.web',
        clockTolerance: 5,                  // seconds
    });
}

// 3) Asymmetric — RSA or EdDSA when the verifier shouldn't have the signing key
import { generateKeyPairSync } from 'node:crypto';
const { privateKey, publicKey } = generateKeyPairSync('ed25519');

const token = jwt.sign({ sub: '42' }, privateKey, { algorithm: 'EdDSA', expiresIn: '15m' });
const payload = jwt.verify(token, publicKey, { algorithms: ['EdDSA'] });

// 4) COMMON MISTAKES
//   a) alg=none accepted — set algorithms explicitly
//   b) Algorithm confusion — server expects RS256 but library treats HMAC
//      Always pass `algorithms: [...]`.
//   c) Putting passwords / sensitive PII in payload — it's BASE64, not encrypted.
//   d) Long-lived access tokens — keep them SHORT (5-15 min) + refresh in HttpOnly cookies.
//   e) Storing JWTs in localStorage — XSS reads them. Prefer HttpOnly cookies.
//   f) No revocation strategy — once issued, can't recall. Keep TTL short + a revocation list (jti → revoked) for emergencies.

// 5) Two-token pattern
//   Access token  — JWT, 15 min, sent in Authorization header (or HttpOnly cookie)
//   Refresh token — random opaque string, 30 days, server-side stored, HttpOnly cookie
//   On refresh: rotate the refresh token, invalidate the old one.

// 6) Revocation list — Redis with jti as key, TTL = remaining token life
await redis.setex(\`revoked:${decoded.jti}\`, decoded.exp - now(), '1');
if (await redis.exists(\`revoked:${decoded.jti}\`)) throw new Error('revoked');

// 7) NEVER trust the alg field from the token — always specify what YOU expect
jwt.verify(token, KEY, { algorithms: ['HS256'] });   // REJECT anything else

Why it matters

algorithms: […] is the most-skipped JWT argument. Without it, alg=none attacks and HMAC / RSA confusion are open doors. Pin the algorithm everywhere you verify.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// JWT: header.payload.signature. Use HS256 (HMAC) for symmetric, EdDSA for asymmetric.
// Set exp, iat, iss, aud. Reject alg=none. Validate signature BEFORE inspecting claims.
const jwt = require('jsonwebtoken');
const token = jwt.sign({ sub }, secret, { algorithm: 'HS256', expiresIn: '15m' });
Try it Yourself »

Exercise

JWT algorithm value to REJECT.

if (header.alg === ' ') reject();

Discussion

Loading…