bhenning.com / dashboard

site6

JWT decoder, builder & best-practices reference (vanilla js)

Sites

site1 site1.bhenning.com site2 site2.bhenning.com site3 site3.bhenning.com site4 site4.bhenning.com site5 site5.bhenning.com site6 site6.bhenning.com site7 site7.bhenning.com site8 site8.bhenning.com site9 site9.bhenning.com

JWT Tool

Paste any JWT below — the demo token was generated with best-practice claims and the secret shown in the verify field.


Best Practices

Do

Use short-lived access tokens

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
Don't

Store tokens in localStorage

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)
Do

Use HttpOnly cookies for storage

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
Don't

Accept the "none" algorithm

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" }
Do

Include all standard claims

Always set iss, sub, aud, exp, iat, and jti on every token you issue.

{ iss, sub, aud, exp, iat, jti }
Don't

Put sensitive data in the payload

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" }
Do

Explicitly specify the algorithm

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 })
Don't

Omit expiration

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" }
Do

Use jti for revocation

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')
Do

Rotate refresh tokens

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)
Do

Use RS256 or ES256 for distributed systems

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
Don't

Trust the alg header blindly

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" }