bhenning.com / dashboard
JWT decoder, builder & best-practices reference (vanilla js)
Paste any JWT below — the demo token was generated with best-practice claims and the secret shown in the verify field.
Fill in claims and a secret to build a signed HS256 JWT. Signing runs in the browser via the Web Crypto API — for demonstration only.
Access tokens should expire in 15 minutes. If stolen, they're only valid for a short window. Use refresh tokens for session persistence.
exp: Math.floor(Date.now()/1000) + 900 // 15 min
localStorage is readable by any JavaScript on the page. A single XSS vulnerability lets an attacker steal every token silently.
// WRONG — XSS steals this immediately
localStorage.setItem('token', jwt)
HttpOnly cookies are inaccessible to JavaScript. Combine with Secure and SameSite=Strict for maximum protection.
Set-Cookie: token=<jwt>;
HttpOnly; Secure; SameSite=Strict;
Path=/; Max-Age=900
Setting alg: "none" disables signature verification entirely. An attacker can forge any payload and the server will accept it.
// VULNERABLE — forged token accepted
{ "alg": "none", "typ": "JWT" }
Always set iss, sub, aud, exp, iat, and jti on every token you issue.
{ iss, sub, aud, exp, iat, jti }
The payload is base64url-encoded, not encrypted. Anyone who holds the token can decode and read it without a key.
// WRONG — anyone can decode this
{ "password": "hunter2",
"ssn": "123-45-6789" }
Hardcode the expected algorithm in your verification logic. Never trust the alg header value from the token itself.
// Node.js / jsonwebtoken
jwt.verify(token, secret, {
algorithms: ['HS256'] // explicit
})
A token without exp is valid forever. If leaked — via logs, caches, or breaches — it can never be invalidated without rotating the signing key.
// WRONG — this token never expires
{ "sub": "user_123", "role": "admin" }
Store issued jti values in Redis or a fast cache. Check on every request to enable immediate token revocation (logout, key rotation).
// On each request
if (await redis.sismember('revoked', jti))
throw new Error('Token revoked')
Issue a new refresh token each time it is used and invalidate the old one. This detects token theft: a reused refresh token signals a compromise.
// On refresh
await revokeToken(old_refresh_jti)
return issueTokenPair(userId)
Asymmetric algorithms let multiple services verify tokens using the public key without ever touching the private signing key.
// Sign with private key (auth server only)
// Verify with public key (any service)
{ "alg": "RS256" } // or ES256
Algorithm confusion attacks switch the algorithm to trick the server. An RS256 public key treated as an HS256 secret lets attackers sign their own tokens.
// VULNERABLE — attacker changes alg to HS256
// and signs with your public key as the secret
{ "alg": "HS256", "typ": "JWT" }