Copynix
Security9 min read

JWT Security Best Practices: A Developer's Guide to Safe Token Handling

JSON Web Tokens are powerful but easy to misuse. This guide covers the most critical JWT security mistakes — algorithm confusion, insecure storage, missing expiration — and how to fix them.

T

Nguyễn Văn Thương

Founder, Copynix

JWTauthenticationsecurity

What Is a JWT?

A JSON Web Token (JWT) is a compact, URL-safe token format defined by RFC 7519. A JWT consists of three Base64URL-encoded parts separated by dots:

header.payload.signature
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTc0MDAwMDAwMH0.abc123

JWTs are used heavily for stateless authentication — the server signs a token at login, the client stores and sends it with every request, and the server verifies the signature without consulting a database.

Critical JWT Vulnerability: The "none" Algorithm

The most dangerous JWT misconfiguration is accepting tokens signed with the alg: "none" algorithm. Some early JWT libraries would accept unsigned tokens if the header specified "alg": "none", allowing attackers to forge tokens for any user.

// Attacker-crafted payload (no real signature)
{"alg":"none","typ":"JWT"}.{"sub":"admin","role":"superuser"}.

Fix: Always explicitly specify the allowed algorithms in your JWT verification library. Never accept "none".

// Node.js — jsonwebtoken library
jwt.verify(token, secret, { algorithms: ["HS256"] });

Algorithm Confusion: RS256 vs HS256

Another classic attack: a server uses RS256 (asymmetric, public/private key pair). The public key is, by definition, public. An attacker takes that public key and creates a token signed with HS256 using the public key as the HMAC secret. If the server doesn't validate the algorithm, it may verify the HMAC against its known "public key" — and accept a forged token.

Fix: Always whitelist the exact algorithm(s) your system uses. Never allow callers to specify the algorithm.

Where to Store JWTs in the Browser

This is one of the most debated topics in frontend security:

StorageXSS riskCSRF riskRecommendation
localStorageHigh — any JS can read itNoneAvoid for auth tokens
sessionStorageHigh — same as aboveNoneAvoid for auth tokens
httpOnly cookieNone — JS cannot accessMediumRecommended
Memory (JS var)Low — lost on refreshNoneGood for short sessions

The consensus for most applications: store JWTs in httpOnly, Secure, SameSite=Strict cookies. This prevents XSS from stealing tokens while CSRF protection comes from SameSite and/or CSRF tokens.

Always Set Token Expiration

A JWT without an expiration claim (exp) is valid forever — if stolen, it gives permanent access. Always set a short expiration for access tokens:

// Access token: 15 minutes
jwt.sign({ sub: userId }, secret, { expiresIn: "15m" });

// Refresh token: 7-30 days (stored securely, used to get new access tokens)
jwt.sign({ sub: userId }, refreshSecret, { expiresIn: "7d" });

The access/refresh token pattern keeps access tokens short-lived (limiting the damage of a stolen token) while maintaining usable sessions via refresh tokens.

Never Put Sensitive Data in the Payload

A JWT payload is Base64URL-encoded — not encrypted. Anyone who holds the token can decode and read the payload. Never include:

Keep payloads lean: user ID, roles, expiration. If you need confidential claims, use JWE (JSON Web Encryption) which adds an encryption layer on top of JWT.

JWT Revocation — The Stateless Tradeoff

JWTs are stateless by design: the server doesn't track which tokens are active. This creates a revocation problem. If a user logs out or you need to invalidate a token (account compromise, role change), you can't simply "delete" it — it remains valid until expiry.

Common revocation strategies:

Validate All Claims

Signature verification is not enough. Always validate:

Good JWT libraries handle this automatically if you configure them correctly. Always read your library's documentation to understand which claims are validated by default.

Key Management

For HS256 (symmetric), the secret key must be at least 256 bits of random data. For RS256/ES256 (asymmetric), use a minimum 2048-bit RSA or 256-bit EC key. Store secrets in environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault) — never hardcode them or commit them to version control.

Rotate keys periodically. When rotating, support a brief overlap period where both old and new keys are accepted, then retire the old key.

Use Copynix to Debug JWTs

During development, use the JWT Decoder to inspect token headers and payloads instantly in your browser — no data sent to any server. The JWT Builder lets you construct and sign tokens for testing your API's verification logic.

Summary

JWT security comes down to a handful of critical practices: whitelist algorithms, set short expirations, store tokens in httpOnly cookies, keep payloads free of sensitive data, and validate all standard claims. Get these right, and JWTs are a solid foundation for stateless authentication.

Free tool

Try it yourself — JWT Decoder

Runs entirely in your browser. No signup, no data sent to servers.

Open JWT Decoder
T

Nguyễn Văn Thương

Founder & Developer · Copynix

Software developer focused on web security and developer tooling. Built Copynix to provide privacy-respecting browser tools that never send your data to a server.

More about the author →

Keep Reading

All articles →
All articlesBrowse tools →