What's in a JWT
Three Base64-URL-encoded parts joined by dots: header.payload.signature. The header says which algorithm signed the token. The payload contains your claims (iss, sub, exp, plus anything you want). The signature is HMAC(header.payload) for HS-* algorithms.
Standard claims
- iss — issuer URL (your auth server).
- sub — subject (the user / client identifier).
- aud — audience (the API that should accept this).
- exp — expiration (Unix timestamp). Verifiers MUST reject tokens past this.
- nbf — not-before (Unix timestamp). Verifiers MUST reject before this.
- iat — issued-at. Auto-set to "now" by this generator.
- jti — JWT ID, unique per token. Useful for revocation lists.
Anything else (role, scope, email, custom flags) goes into the payload as additional fields. Don't put secrets in claims — JWT claims are signed, not encrypted.
HS256 vs HS384 vs HS512
All three are HMAC-based and use a shared secret. Larger digest = stronger collision resistance but no practical security gain — HS256 is the de-facto default and what every library supports without configuration. Use HS384 or HS512 only if your spec requires it.
Verify your JWT
Paste the generated token + the secret into jwt.io — it will decode the header / payload and confirm the signature is valid. Or in code: jwt.verify(token, secret, { algorithms: ['HS256'] }) in Node, JWT::decode($token, new Key($secret, 'HS256')) in PHP (firebase/php-jwt).