JWT Vulnerabilities in Vibe Coded Apps
How AI tools misconfigure JWT authentication, letting attackers forge tokens
JWT authentication is often misconfigured by AI tools - weak secrets, missing algorithm validation, and no expiration. These flaws let attackers forge tokens and impersonate any user. Always specify the algorithm explicitly and use strong, random secrets.
Source: OWASP Top 10 (2021) - JWT issues fall under Cryptographic Failures
What is JWT and why does it matter?
JWT (JSON Web Token) is a compact, URL-safe way to represent claims between two parties. It's widely used for authentication in APIs and single-page applications. A JWT has three parts separated by dots: header.payload.signature. The signature proves the token hasn't been tampered with.
JWTs are ubiquitous in vibe coded applications. When you ask AI for "user authentication" or "API auth," you often get JWT implementation. The problem is AI tools generate the simplest working code - which is usually insecure.
According to Auth0's 2015 disclosure, critical vulnerabilities in JWT libraries allowed attackers to forge tokens. These same patterns still appear in AI-generated code today. CWE-347 describes improper signature verification, which is exactly what happens when AI generates JWT code without algorithm specification.
The 5 JWT flaws AI tools get wrong
These five vulnerabilities appear consistently in vibe coded JWT implementations:
Algorithm confusion
Using RS256 without specifying the algorithm in verify(), allowing attackers to switch to HS256 and sign with the public key.
None algorithm
Libraries that accept tokens with alg: "none", requiring no signature at all.
Weak secrets
AI generates short or predictable secrets like 'secret' or 'myapp-key' that can be brute-forced.
Missing expiration
Tokens without exp claim work forever. Stolen tokens remain valid indefinitely.
Insecure storage
Storing JWTs in localStorage makes them vulnerable to XSS attacks.
How does algorithm confusion work?
Algorithm confusion is the most dangerous JWT vulnerability. It exploits the difference between symmetric (HS256) and asymmetric (RS256) algorithms.
RS256 (Asymmetric)
- Sign with private key
- Verify with public key
- Public key is... public
HS256 (Symmetric)
- Sign with secret
- Verify with same secret
- Secret must stay private
The attack
- Server uses RS256, verifies with public key
- Attacker changes token header to
{"alg": "HS256"} - Attacker signs token with the public key (which is public!)
- Server calls
jwt.verify(token, publicKey) - Since algorithm isn't specified, library treats publicKey as HS256 secret
- Signature validates - attack succeeds!
According to PortSwigger, this attack works because developers don't specify which algorithm to accept during verification. The library trusts whatever algorithm is in the token header.
Why do AI tools misconfigure JWT?
AI tools generate the simplest working JWT code. "Simple" and "working" don't mean "secure."
Dangerous pattern: AI-generated JWT verification
When you ask AI for JWT authentication, it often generates this:
// VULNERABLE: AI generates this without algorithm specification
const jwt = require('jsonwebtoken')
app.get('/protected', (req, res) => {
const token = req.headers.authorization?.split(' ')[1]
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET) // DANGEROUS!
req.user = decoded
// ... protected logic
} catch (err) {
res.status(401).json({ error: 'Invalid token' })
}
})
// Without specifying algorithms, attacker can use algorithm confusion!The code works for legitimate tokens. But without algorithms: ['HS256'], it accepts any algorithm the attacker specifies.
Dangerous pattern: Weak secrets
// VULNERABLE: Weak secrets AI tools generate
const JWT_SECRET = 'secret' // Cracked in seconds
const JWT_SECRET = 'myapp-secret-key' // Dictionary words
const JWT_SECRET = '12345678' // Numeric, very weak
const JWT_SECRET = process.env.JWT_SECRET || 'fallback' // Fallback in prod!
// Attacker uses hashcat or jwt_tool to crack:
// hashcat -m 16500 jwt.txt wordlist.txt
// jwt_tool -C -d wordlist.txt jwt_tokenTools like Cursor, Bolt, and Claude Code all generate these patterns because you didn't explicitly ask for secure JWT handling.
How do I detect JWT vulnerabilities?
Search for JWT verification without algorithm specification and weak/hardcoded secrets.
// jwt.verify() without algorithms option (DANGEROUS)
jwt.verify(token, secret)
jwt.verify(token, publicKey)
// jwt.sign() without expiresIn (DANGEROUS)
jwt.sign(payload, secret)
jwt.sign({ userId }, process.env.JWT_SECRET)
// Hardcoded or weak secrets (DANGEROUS)
const JWT_SECRET = 'secret'
const secret = 'my-jwt-secret'
JWT_SECRET || 'fallback'
// localStorage JWT storage (DANGEROUS)
localStorage.setItem('token', jwt)
localStorage.getItem('token')
// Regex to find vulnerable patterns:
// jwt\.verify\s*\([^)]+\)\s*(?!.*algorithms)
// jwt\.sign\s*\([^)]+\)\s*(?!.*expiresIn)Don't want to search manually?
Scan your code freeHow do I fix JWT vulnerabilities?
Fix JWT vulnerabilities by always specifying the algorithm, using strong secrets, and setting proper expiration.
AI Fix Prompt
Copy this prompt into Cursor, Claude Code, or Bolt to automatically fix JWT issues in your codebase:
Manual Fix
The fix is: always specify algorithms, use strong secrets, and set expiration.
// No algorithm, weak secret, no expiration
const jwt = require('jsonwebtoken')
const JWT_SECRET = 'secret'
const token = jwt.sign({ userId: user.id }, JWT_SECRET)
const decoded = jwt.verify(token, JWT_SECRET)
// Attacker can:
// 1. Brute-force 'secret' in seconds
// 2. Use algorithm confusion if RS256
// 3. Use stolen token forever// Algorithm specified, strong secret, expiration set
const jwt = require('jsonwebtoken')
const JWT_SECRET = process.env.JWT_SECRET // 32+ byte random
const token = jwt.sign(
{ sub: user.id, email: user.email },
JWT_SECRET,
{
algorithm: 'HS256',
expiresIn: '1h',
issuer: 'myapp',
}
)
const decoded = jwt.verify(token, JWT_SECRET, {
algorithms: ['HS256'], // Only accept HS256
issuer: 'myapp',
})
// Now attacker cannot:
// 1. Brute-force strong secret
// 2. Use algorithm confusion (pinned to HS256)
// 3. Use expired tokensGenerate a secure secret
Run this once to generate a proper JWT secret:
# Node.js
node -e "console.log('JWT_SECRET=' + require('crypto').randomBytes(32).toString('base64'))"
# OpenSSL
openssl rand -base64 32
# Output example:
# JWT_SECRET=K8Hy5+9cLj3xR2mN0pQwVbGfTdZaYuXkIo1hEs7C4nM=
# Add to your .env file and NEVER commit itFrequently asked questions
What are common JWT vulnerabilities?
The most common JWT vulnerabilities are: algorithm confusion (changing RS256 to HS256), accepting the "none" algorithm, weak/guessable secrets, missing expiration claims, and insecure token storage. AI tools often generate code with weak secrets and no algorithm specification, making tokens easy to forge.
How do I secure JWT tokens?
Always specify the expected algorithm in verify() - never let the token dictate the algorithm. Use cryptographically random secrets at least 256 bits (32 bytes) long. Set reasonable expiration times with the "exp" claim. Store tokens in HttpOnly cookies, not localStorage. Verify issuer and audience claims.
What is JWT algorithm confusion attack?
Algorithm confusion happens when you use RS256 (asymmetric) but your code accepts HS256 (symmetric). Attackers change the token's algorithm to HS256 and sign with your public key. Since the server uses the same public key to verify and HS256 uses symmetric signing, the forged token validates successfully.
Should I use JWT for authentication?
JWTs are fine for authentication when implemented correctly - the problem is most implementations are not correct. If you need simple session management, server-side sessions may be safer. If you need stateless auth across services, JWTs work well but require careful implementation of algorithm pinning, strong secrets, and proper expiration.
How long should JWT secrets be?
JWT secrets should be at least 256 bits (32 bytes) of cryptographically random data. Short or predictable secrets can be brute-forced using tools like hashcat. Generate secrets using crypto.randomBytes(32).toString("base64") and store them securely in environment variables, never in code.
Related content
External resources
- PortSwigger JWT Attacks - Comprehensive JWT security guide
- Auth0 JWT Vulnerabilities - Original library vulnerability disclosure
- OWASP JWT Cheat Sheet - Best practices guide
- RFC 7519 - Official JWT specification
- CWE Top 25 - Most dangerous software weaknesses
- jwt.io - JWT debugger tool
Scan your code for JWT vulnerabilities
Check your codebase for JWT misconfigurations and other authentication vulnerabilities in AI-generated code.
Try Vibeship Scanner