Quick Answer
AI-generated JWT implementations typically have three critical flaws: hardcoded secrets (often "secret" or "your-secret-key"), no algorithm restriction allowing the "none" algorithm attack, and missing token expiry. These three issues together let attackers forge valid tokens and access any account in your application.
Why AI Gets JWT Wrong
JSON Web Tokens (JWT) are the most common authentication mechanism in AI-generated APIs. Every AI coding tool, from Cursor to Bolt to Lovable, generates JWT-based authentication when asked for user login. The problem is that working JWT code and secure JWT code are very different things.
According to the Auth0 2024 State of Identity Security report, JWT misconfiguration is the most common authentication vulnerability in modern web applications. The OWASP Testing Guide specifically calls out algorithm confusion and weak secrets as top JWT risks. AI tools generate code that demonstrates these exact vulnerabilities.
A 2023 PortSwigger research study found that 25% of web applications using JWT had at least one exploitable implementation flaw. When AI generates the JWT code, that number climbs significantly because the models learn from tutorial-quality examples, not production-hardened implementations.
Vulnerability 1: Hardcoded JWT Secrets
// ❌ BAD - AI's favorite JWT patterns
const token = jwt.sign(payload, 'secret');
const token = jwt.sign(payload, 'your-secret-key');
const token = jwt.sign(payload, 'jwt-secret-123');
const token = jwt.sign(payload, process.env.JWT_SECRET || 'fallback-secret');
// ✅ GOOD - Strong secret, no fallback
// Generate with: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
const token = jwt.sign(payload, process.env.JWT_SECRET, {
algorithm: 'HS256',
expiresIn: '1h',
});
// JWT_SECRET must be at least 256 bits (64 hex chars) for HS256
Vulnerability 2: The "none" Algorithm Attack
The JWT specification includes an "alg": "none" option that creates unsigned tokens. If your verification code does not explicitly restrict algorithms, an attacker can modify a token, set the algorithm to "none", remove the signature, and your server will accept it as valid.
// ❌ BAD - No algorithm restriction
const decoded = jwt.verify(token, secret);
// Accepts ANY algorithm, including "none"
// ❌ BAD - Algorithm confusion: RS256 key used with HS256
const decoded = jwt.verify(token, publicKey);
// Attacker can use the public key as HMAC secret
// ✅ GOOD - Explicitly restrict algorithm
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'], // ONLY accept HS256
complete: true,
});
Vulnerability 3: Missing or Excessive Token Expiry
AI-generated code either sets no expiry at all (tokens valid forever) or sets extremely long expiry periods. A stolen token with no expiry gives permanent access to the victim's account.
| Token Type | AI Default | Secure Default | Why |
|---|---|---|---|
| Access token | No expiry or 30 days | 15-60 minutes | Limits damage window if stolen |
| Refresh token | Not implemented | 7-30 days, rotated on use | Allows session extension safely |
| Password reset | 24 hours or no expiry | 15-30 minutes | Minimizes attack window |
| Email verification | No expiry | 24 hours | Prevents stale verification links |
Complete Secure JWT Implementation
// ✅ GOOD - Production-ready JWT setup
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET || JWT_SECRET.length < 64) {
throw new Error('JWT_SECRET must be set and at least 64 characters');
}
function generateTokens(userId) {
const accessToken = jwt.sign(
{ sub: userId, type: 'access' },
JWT_SECRET,
{ algorithm: 'HS256', expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ sub: userId, type: 'refresh' },
JWT_SECRET,
{ algorithm: 'HS256', expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
function verifyAccessToken(token) {
return jwt.verify(token, JWT_SECRET, {
algorithms: ['HS256'],
complete: true,
});
}
How to Audit Your JWT Implementation
- Search for hardcoded secrets: Grep for
jwt.signand check the secret parameter. If it is a string literal, replace with an env var. - Check algorithm restrictions: Every
jwt.verifycall must include{ algorithms: ['HS256'] }(or your chosen algorithm). - Verify token expiry: Every
jwt.signcall should includeexpiresIn. - Look for refresh token rotation: If your app uses refresh tokens, ensure they are rotated (old token invalidated) on each use.
- Run automated scanning: Tools like VibeDoctor (vibedoctor.io) automatically detect hardcoded JWT secrets and insecure token patterns in your codebase. Free to sign up.
FAQ
Should I use JWT or session cookies for authentication?
For most web applications, session cookies with httpOnly and Secure flags are simpler and harder to get wrong. JWT is better for stateless APIs, microservices, and mobile app backends. If you are building a standard Next.js app, consider using NextAuth or Clerk instead of implementing JWT yourself.
What is algorithm confusion in JWT?
Algorithm confusion (or key confusion) happens when a server using RS256 (asymmetric) does not restrict the algorithm. An attacker can switch the algorithm to HS256 (symmetric) and sign the token with the public key, which the server then accepts. Always specify algorithms: ['RS256'] or algorithms: ['HS256'] in your verify call.
How long should my JWT secret be?
For HS256, the secret must be at least 256 bits (32 bytes, or 64 hex characters). Use crypto.randomBytes(64).toString('hex') to generate a secure secret. Never use dictionary words, short strings, or common phrases. The secret's entropy directly determines how resistant it is to brute-force attacks.
Can I invalidate a JWT before it expires?
Not without additional infrastructure. JWTs are stateless - once issued, they are valid until expiry. To enable early revocation, maintain a blocklist of revoked token IDs (the jti claim) in Redis or your database. This is why short access token expiry (15 minutes) paired with refresh tokens is the recommended pattern.