When AI Gets Authentication Wrong
Authentication is hard. AI makes it look easy—and that's the problem. AI-generated auth code frequently contains bypasses that let attackers access accounts without proper credentials.
The Authentication Attack Surface
Every authentication system has potential bypass points:
User → Login Form → Validation → Session → Protected Resource
↑ ↑ ↑ ↑
Bypass 1 Bypass 2 Bypass 3 Bypass 4AI-generated code often fails at multiple points.
Bypass Pattern 1: Client-Side Checks Only
AI-Generated (Vulnerable):
// pages/dashboard.tsx
export default function Dashboard() {
const { user } = useContext(AuthContext) if (!user) {
redirect('/login')
}
return
}
The Problem: This check only runs client-side. An attacker can:
- Disable JavaScript
- Modify the React state
- Call APIs directly
// app/dashboard/page.tsx
import { getServerSession } from 'next-auth'
import { redirect } from 'next/navigation'export default async function Dashboard() {
const session = await getServerSession()
if (!session) {
redirect('/login')
}
return
}
Bypass Pattern 2: Missing API Protection
AI-Generated (Vulnerable):
// api/users/route.ts
export async function GET() {
const users = await db.query('SELECT * FROM users')
return Response.json(users)
}The Problem: Anyone can access this endpoint. No auth check.
The Fix:
// api/users/route.ts
import { getServerSession } from 'next-auth'export async function GET() {
const session = await getServerSession()
if (!session) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
if (!session.user.isAdmin) {
return Response.json({ error: 'Forbidden' }, { status: 403 })
}
const users = await db.query('SELECT id, email, name FROM users')
return Response.json(users)
}
Bypass Pattern 3: Predictable Tokens
AI-Generated (Vulnerable):
// Generate password reset token
function generateResetToken(userId) {
return Buffer.from(userId + Date.now()).toString('base64')
}The Problem: Tokens are predictable. Attacker can:
- Guess the user ID
- Estimate the timestamp
- Generate valid tokens
import crypto from 'crypto'function generateResetToken() {
return crypto.randomBytes(32).toString('hex')
}
// Store token hash in database (never store token directly)
const tokenHash = crypto.createHash('sha256').update(token).digest('hex')
Bypass Pattern 4: JWT Without Verification
AI-Generated (Vulnerable):
// Decode JWT without verification
function getUser(req) {
const token = req.headers.authorization?.split(' ')[1]
const decoded = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString())
return decoded
}The Problem: No signature verification. Anyone can create a fake JWT:
// Attacker creates:
const fakePayload = { userId: 1, isAdmin: true }
const fakeToken = 'xxx.' + btoa(JSON.stringify(fakePayload)) + '.xxx'The Fix:
import jwt from 'jsonwebtoken'function getUser(req) {
const token = req.headers.authorization?.split(' ')[1]
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET)
return decoded
} catch {
return null
}
}
Bypass Pattern 5: Session Fixation
AI-Generated (Vulnerable):
app.post('/login', async (req, res) => {
const user = await authenticate(req.body) if (user) {
req.session.userId = user.id // Session ID unchanged
res.redirect('/dashboard')
}
})
The Problem: Session ID stays the same after login. Attacker can:
- Get victim to use a known session ID
- Wait for victim to log in
- Use the same session to access victim's account
app.post('/login', async (req, res) => {
const user = await authenticate(req.body) if (user) {
// Regenerate session ID
req.session.regenerate((err) => {
if (err) return res.status(500).json({ error: 'Session error' })
req.session.userId = user.id
res.redirect('/dashboard')
})
}
})
Bypass Pattern 6: Timing-Based Username Enumeration
AI-Generated (Vulnerable):
app.post('/login', async (req, res) => {
const { email, password } = req.body const user = await User.findByEmail(email)
if (!user) {
return res.json({ error: 'User not found' }) // Fast response
}
const valid = await bcrypt.compare(password, user.passwordHash)
if (!valid) {
return res.json({ error: 'Invalid password' }) // Slow response
}
})
The Problem: Different response times reveal if email exists.
The Fix:
app.post('/login', async (req, res) => {
const { email, password } = req.body const user = await User.findByEmail(email)
// Always do password comparison (even with fake hash)
const hash = user?.passwordHash ?? '$2b$12$fakehashfakehashfakehash'
const valid = await bcrypt.compare(password, hash)
if (!user || !valid) {
return res.json({ error: 'Invalid credentials' }) // Generic message
}
// Login successful
})
Authentication Security Checklist
LOGIN
=====
[ ] Server-side session validation
[ ] Password hashing with bcrypt (cost ≥ 12)
[ ] Rate limiting on login endpoint
[ ] Account lockout after failed attempts
[ ] Generic error messages (no enumeration)
[ ] Session regeneration on loginSESSIONS
========
[ ] Cryptographically random session IDs
[ ] HttpOnly cookie flag
[ ] Secure cookie flag (HTTPS only)
[ ] SameSite cookie attribute
[ ] Reasonable expiration time
[ ] Server-side session storage
TOKENS (JWT)
============
[ ] Proper signature verification
[ ] Secret key in environment variable
[ ] Appropriate expiration time
[ ] No sensitive data in payload
[ ] Token refresh mechanism
PASSWORD RESET
==============
[ ] Cryptographically random tokens
[ ] Token expiration (1 hour max)
[ ] One-time use tokens
[ ] Rate limiting on reset requests
[ ] No token in URL (use POST)
Testing for Auth Bypass
Manual Tests
- Skip client-side: Disable JavaScript, access protected pages
- Direct API access: Call protected endpoints without auth header
- Token manipulation: Modify JWT payload, observe behavior
- Session tampering: Change session cookie, check access
- Timing attack: Measure response times for valid/invalid users
Automated Checks
// Test unauthorized access
const tests = [
{ url: '/api/users', expectedStatus: 401 },
{ url: '/api/admin', expectedStatus: 401 },
{ url: '/dashboard', expectedRedirect: '/login' },
]for (const test of tests) {
const res = await fetch(test.url, { redirect: 'manual' })
console.assert(res.status === test.expectedStatus)
}
The Bottom Line
Authentication bypass is the #1 vulnerability in AI-generated code. Always verify:
- Server-side authentication on every protected resource
- Proper session management
- Cryptographically secure tokens
- No timing or enumeration leaks