JWT Usage Patterns: Common Mistakes Developers Make
JSON Web Tokens (JWTs) are the de facto standard for stateless authentication in web applications. With 407.8 million monthly npm downloads across the jose and jsonwebtoken packages, and 18,477 Stack Overflow questions, JWTs are both heavily adopted and frequently misunderstood.
The disproportionate ratio of Stack Overflow questions to download volume signals a fundamental problem: JWT is easy to use but hard to use correctly. This article catalogs the most common JWT anti-patterns, explains why they are dangerous, and provides secure alternatives with working code examples.
The JWT Ecosystem in 2026
Before diving into mistakes, here is where the JWT package ecosystem stands today:
| Package | Monthly Downloads | Share | Key Characteristics |
|---|---|---|---|
| jose | 249.9M | 61.3% | Full JOSE spec, Web Crypto API, multi-runtime (Node/Deno/Bun/browser) |
| jsonwebtoken | 157.9M | 38.7% | Node.js focused, callback-based API, depends on jws and jwa |
The Seven Most Common JWT Mistakes
Mistake 1: Not Validating the Algorithm
The algorithm confusion attack is the most dangerous JWT vulnerability. It exploits servers that read the alg field from the token header instead of enforcing an expected algorithm.
How it works: If a server uses RS256 (asymmetric), the verification process uses the RSA public key. An attacker modifies the token header to "alg": "HS256" (symmetric) and signs the token using the public key as the HMAC secret. Since the public key is often publicly available, the server unknowingly verifies the forged token using the same public key as an HMAC secret, and the signature matches.
Mistake 2: No Token Expiration
Tokens without an exp (expiration) claim are valid forever. If a token is leaked, stolen, or intercepted, it provides permanent access to the system.
Mistake 3: Storing Sensitive Data in the Payload
JWT payloads are Base64URL-encoded, not encrypted. Anyone with access to the token can decode the payload and read every claim. You can verify this yourself — paste any JWT into KappaKit's JWT Decoder and the payload is instantly readable.
Sensitive data in payloadIf you must transmit confidential data in a token, use JWE (JSON Web Encryption) instead of JWS (JSON Web Signing). The jose library supports both:
Mistake 4: Using Weak Signing Secrets
HMAC-based JWT algorithms (HS256, HS384, HS512) derive their security from the secret key. A short or predictable secret can be brute-forced, allowing an attacker to forge valid tokens.
Weak secretsFor HS256, the secret must be at least 256 bits (32 bytes). For RS256, use RSA keys of at least 2048 bits. For ES256, use P-256 (secp256r1) curve keys.
Mistake 5: Storing JWTs in localStorage
localStorage is accessible to any JavaScript running on the page, including third-party scripts and XSS payloads. Storing JWTs in localStorage exposes them to cross-site scripting attacks.
localStorage storagehttpOnly cookies cannot be accessed by JavaScript. Combined with Secure (HTTPS-only) and SameSite=Strict (blocks cross-origin requests), this provides defense in depth against both XSS and CSRF attacks.
Mistake 6: Not Validating Issuer and Audience
A JWT signed by one service can be replayed against another service if both use the same signing key. Without iss (issuer) and aud (audience) validation, a token meant for your staging environment could be used in production, or a token from a microservice could be replayed against a different one.
Mistake 7: Using the "none" Algorithm in Production
The JWT specification includes an "alg": "none" option for unsigned tokens. Some JWT libraries accept none by default, allowing attackers to remove the signature entirely and have the token accepted as valid.
jwt.decode() parses the token without verifying the signature. It should only be used for inspecting token contents (debugging), never for authentication decisions. Always use jwt.verify() with explicit algorithm restrictions for any security-relevant token validation.
JWT Security Checklist
Use this checklist to audit your JWT implementation:
| Check | Action | Risk if Missing |
|---|---|---|
| Algorithm enforcement | Pass algorithms array to verify() |
Complete authentication bypass |
| Token expiration | Set exp claim (5-15 min for access tokens) |
Permanent access from stolen tokens |
| Payload minimization | Only include identifiers, not sensitive data | Data leakage from intercepted tokens |
| Strong secret | 256+ bit random secret for HMAC; 2048+ bit RSA keys | Token forgery via brute force |
| Secure storage | httpOnly + Secure + SameSite cookies | Token theft via XSS |
| Issuer validation | Verify iss claim against expected value |
Cross-service token replay |
| Audience validation | Verify aud claim against expected value |
Cross-environment token replay |
| Reject "none" algorithm | Explicitly list allowed algorithms | Unsigned token acceptance |
jsonwebtoken vs. jose: Migration Guide
If you are still using jsonwebtoken (38.7% of the market), here is how the two libraries compare for common operations:
| Operation | jsonwebtoken | jose |
|---|---|---|
| Sign | jwt.sign(payload, secret, opts) |
new SignJWT(payload).setProtectedHeader({alg}).sign(key) |
| Verify | jwt.verify(token, key, opts) |
jwtVerify(token, key, opts) |
| Decode (no verify) | jwt.decode(token) |
decodeJwt(token) |
| Async | Callback-based | Promise-based (async/await) |
| Crypto backend | Node.js crypto (OpenSSL) | Web Crypto API (native) |
| Runtime support | Node.js only | Node.js, Deno, Bun, browsers |
| JWE (encryption) | Not supported | Full support |
To inspect tokens during development and debugging, use KappaKit's JWT Decoder. It runs entirely in your browser and never transmits your token to any server — critical when debugging production tokens that may contain real user data.
Key Takeaways
- Always specify allowed algorithms. The algorithm confusion attack is the single most dangerous JWT vulnerability, and it is trivially preventable with one configuration option.
- Set short expiration times. 15-minute access tokens with 7-day refresh tokens provide a good balance between security and user experience.
- Never store sensitive data in JWT payloads. The payload is Base64URL-encoded, not encrypted. Anyone can read it.
- Use httpOnly cookies, not localStorage. httpOnly cookies are immune to XSS attacks; localStorage is not.
- Migrate from jsonwebtoken to jose. jose uses native Web Crypto, supports all modern runtimes, and implements the full JOSE specification including encryption.
- Validate issuer and audience. These claims prevent cross-service and cross-environment token replay attacks.
Methodology & Sources
Download data: npm registry API (api.npmjs.org/downloads/point/last-month), collected April 7, 2026.
Stack Overflow data: Stack Exchange API v2.3 (api.stackexchange.com/2.3/tags/jwt/info), collected April 7, 2026. The 18,477 figure represents cumulative questions tagged with "jwt" on Stack Overflow.
Vulnerability references: Algorithm confusion (CVE-2015-9235), "none" algorithm bypass (RFC 7518 Section 3.6), and localStorage XSS vectors are well-documented in OWASP JWT Security Cheat Sheet and the Auth0 JWT Handbook.
Code examples: All code samples use the jsonwebtoken (v9.x) and jose (v5.x) APIs current as of April 2026. Test all security-critical code in your specific environment before deploying.
Frequently Asked Questions
What is the most common JWT security mistake?
The most common JWT security mistake is not validating the algorithm (alg) field in the token header. This enables algorithm confusion attacks where an attacker changes alg from RS256 to HS256, causing the server to use the public key as an HMAC secret and bypassing signature verification entirely. Always specify allowed algorithms explicitly.
Should I use jsonwebtoken or jose in 2026?
jose is the recommended choice. It accounts for 61% of JWT downloads (249.9M/month), uses native Web Crypto APIs, supports all modern runtimes (Node.js, Deno, Bun, browsers), and implements the full JOSE specification including JWE encryption. jsonwebtoken is Node.js-only and uses older crypto primitives.
Is it safe to store sensitive data in a JWT payload?
No. JWT payloads are Base64URL-encoded, not encrypted. Anyone with access to the token can decode the payload and read its contents. Never store passwords, credit card numbers, or other sensitive data in JWT claims. Use JWE for encrypted payloads or store sensitive data server-side.
What is a good JWT expiration time?
Access tokens should expire in 5 to 15 minutes. Refresh tokens can last 7 to 30 days but should be stored securely in httpOnly cookies, not localStorage. Short-lived access tokens limit the damage window if a token is compromised.
How do I revoke a JWT token?
JWTs are stateless and cannot be revoked directly. Common strategies include: short expiration times, server-side token blocklists, refresh token rotation (issuing new tokens on each use), and token versioning (incrementing a per-user version counter).
Can I decode a JWT without the secret key?
Yes. The header and payload are Base64URL-encoded, not encrypted. Anyone can decode them. The signature requires the secret key to verify, but reading the contents requires no key. This is why sensitive data should never be stored in JWT claims.
What JWT algorithms should I use?
For symmetric signing, use HS256 with a 256-bit random secret. For asymmetric signing, use RS256 (2048-bit RSA keys) or ES256 (ECDSA P-256) for smaller keys and faster verification. Never use the "none" algorithm in production.