A team ships a Node.js API with JWTs for auth. Six months later, a security researcher reports they can forge tokens for any user by sending "alg": "none" in the header. The fix is a one-line change. The embarrassment — and the audit that follows — is not.
JWT vulnerabilities aren't exotic. They're almost always caused by misreading the spec, trusting library defaults, or skipping claim validation under deadline pressure. Here's the checklist that prevents all of it.
JWT Anatomy — click a section to inspect it
Base64url-decoded · Signature verified server-side only · Payload is readable without a key
Never accept alg: none
The none algorithm is technically valid per the original JWT spec — it means "unsigned, trust me." Most mature libraries disable it by default now, but not all, and misconfiguration still happens constantly.
Always explicitly specify the allowed algorithms when verifying a token. Don't let the token header dictate which algorithm your server uses. The attack is straightforward: an attacker takes a valid token, strips the signature, sets "alg": "none", and some libraries will happily verify it as valid.
The RS256 vs HS256 confusion attack
This one catches people off guard. If your server uses RS256 (asymmetric), it verifies tokens with the public key. An attacker who knows your public key — and it's often published at a JWKS endpoint — can sign a forged token using that public key as an HMAC secret and set "alg": "HS256". If your server doesn't pin the expected algorithm, it verifies the HS256 signature using the public key as the HMAC secret, which the attacker controls.
The fix is the same: hardcode the expected algorithm server-side. Never let the incoming token header influence which algorithm you use for verification.
JWT Attack Simulator — select a scenario and press Play
Press Play to walk through the attack
Your signing secret is probably too weak
"secret", "mysecret", "jwt_secret" — these appear in breach databases more often than you'd think because developers copy them from tutorials and forget to change them. For HS256, the HMAC key should be at least 256 bits of entropy. For HS512, use 512 bits.
A weak secret means your tokens can be brute-forced offline. Once an attacker has one valid token, they can run tools like hashcat or jwt_tool against it without touching your server.
Use a cryptographically random secret with enough entropy — the random secret generator outputs these directly. Store it in an environment variable or secrets manager, never in source code.
Validate every claim you care about
Signing proves integrity. It does not prove the token is usable right now by this user for this purpose. You have to validate claims explicitly.
The minimum you should verify:
exp— token hasn't expirediss— issuer matches your expected valueaud— audience is your service, not some other service that shares the same signing keysub— subject is a user that still exists and is still active in your database
The aud claim is the most commonly skipped. If you have multiple services sharing a signing key (please don't, but if you do), a token issued for Service A is technically cryptographically valid for Service B if you're not checking audience. That's an authorization bypass.
Keep tokens short-lived and use refresh tokens properly
Access tokens should expire in 15 minutes to an hour. Not 24 hours, not 30 days. The reason is that JWTs are stateless — you can't invalidate a single token without either storing a blocklist (which defeats part of the stateless benefit) or waiting for it to expire naturally.
If a token is stolen — via XSS, a log leak, or a compromised third party — a short expiry limits the blast radius. Pair short-lived access tokens with longer-lived refresh tokens stored in an HttpOnly cookie (not localStorage). The refresh token endpoint should rotate the refresh token on each use, which lets you detect token theft: if a reused refresh token comes in, someone is replaying a stolen token and you can invalidate the entire session chain.
Don't put sensitive data in the payload
JWT payloads are base64-encoded, not encrypted. Anyone with the token can decode the payload — no key needed. This isn't a vulnerability by itself, but storing PII, roles with sensitive context, or internal system details in the payload leaks that data to anyone who intercepts or logs the token.
If you need the payload to be confidential, use JWE (JSON Web Encryption) instead of JWS. Otherwise, treat the JWT payload as something that could end up in a browser's network tab, a CDN access log, or an error report.
Watch your key rotation strategy
If you ever rotate your signing secret, you need a grace period where both old and new keys are accepted. Most teams don't think about this until they rotate a key in production and immediately log out every active user. Use key IDs (kid header claim) so your verification logic can select the right key for a given token without trying all of them on every request.
HTTP headers and storage
Store access tokens in memory (JavaScript variable or app state) on the frontend, not localStorage. localStorage is accessible to any JavaScript running on the page, which makes stored tokens an XSS target. HttpOnly cookies are better for refresh tokens — they're not accessible to JavaScript at all, only sent automatically with requests.
The gotcha: if you put your access token in a cookie, you need CSRF protection. If you put it in memory, you need to handle page refreshes (the token is gone — use the refresh token cookie to silently reissue it on load). Neither approach is free.
Generate a proper signing secret for your next JWT implementation using the random secret generator — it outputs cryptographically random values at the bit length you specify, ready to drop into your environment config.
Frequently Asked Questions
Related posts
Secure Password Reset Tokens — Expiry, Storage, and What Most Implementations Get Wrong
A practical guide to building secure password reset flows: token generation, expiry windows, one-time use enforcement, and the edge cases that cause real account takeovers.
Mar 30, 2026 · 7 min readIncident Response for Developers: What to Do When You Get Hacked
A practical incident response guide for developers covering detection, containment, eradication, recovery, and communication when a security breach happens.
Mar 29, 2026 · 9 min readPhishing Prevention: A Developer's Guide to SPF, DKIM, and DMARC
Understand how email spoofing enables phishing attacks and how to implement SPF, DKIM, and DMARC to protect your domain from being impersonated.
Mar 29, 2026 · 9 min read