TL;DR
Broken authentication covers any weakness in login and session management. The two problems we see most in scans: no rate limiting on the login endpoint, and passwords stored as plain text or hashed with MD5. Use an established auth provider if you can (Clerk, Auth0, Supabase Auth). If you're building your own, bcrypt for passwords and a rate limiter on every login endpoint are non-negotiable. Make sure logout actually invalidates sessions server-side too.
What Is Broken Authentication?
Broken authentication is a category of vulnerabilities in how users log in and stay logged in. When it's broken, attackers can:
- Guess or brute-force passwords without rate limiting
- Steal or hijack session tokens
- Bypass authentication entirely
- Take over accounts through password reset flaws
- Access accounts through credential stuffing (using leaked passwords)
Common Authentication Vulnerabilities
1. Weak Password Storage
// CRITICAL VULNERABILITY: Plain text passwords
const user = await db.user.create({
data: { email, password } // Password stored as-is!
});
// WRONG: Using weak hashing
const hash = crypto.createHash('md5').update(password).digest('hex');
// WRONG: Missing salt
const hash = crypto.createHash('sha256').update(password).digest('hex');
import bcrypt from 'bcrypt';
// Hashing a password (on signup)
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(password, saltRounds);
// Verifying a password (on login)
const isValid = await bcrypt.compare(password, user.hashedPassword);
2. No Rate Limiting
Without rate limiting, attackers can try thousands of passwords:
// Attacker can send unlimited login attempts
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
const user = await authenticate(email, password);
// No rate limiting = brute force attacks work
});
import rateLimit from 'express-rate-limit';
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts per window
message: 'Too many login attempts, please try again later',
standardHeaders: true,
legacyHeaders: false,
});
app.post('/api/login', loginLimiter, async (req, res) => {
// Now protected against brute force
});
3. Insecure Session Management
| Vulnerability | Problem | Fix |
|---|---|---|
| Predictable session IDs | Attackers can guess valid sessions | Use cryptographically random IDs |
| Session in URL | Token leaked in browser history, referrer | Use HttpOnly cookies |
| No session expiration | Stolen sessions work forever | Set reasonable expiration times |
| Session not invalidated on logout | Old sessions still work | Destroy session server-side on logout |
4. Insecure Password Reset
// VULNERABLE: Predictable reset tokens
const resetToken = Date.now().toString();
const resetUrl = `/reset?token=${resetToken}`;
// VULNERABLE: No expiration
await db.passwordReset.create({
data: { userId: user.id, token: resetToken }
// Missing: expiresAt field
});
// VULNERABLE: Token not invalidated after use
// User could reuse the same reset link
import crypto from 'crypto';
// Generate secure random token
const resetToken = crypto.randomBytes(32).toString('hex');
const tokenHash = crypto.createHash('sha256').update(resetToken).digest('hex');
// Store hashed token with expiration
await db.passwordReset.create({
data: {
userId: user.id,
tokenHash,
expiresAt: new Date(Date.now() + 3600000) // 1 hour
}
});
// After password reset, delete the token
await db.passwordReset.delete({ where: { tokenHash } });
Authentication in AI-Generated Code
AI-generated auth code tends to repeat the same mistakes:
Common AI auth mistakes: Plain-text password storage, missing rate limiting, and JWT tokens with no expiration. Hardcoded secrets and missing CSRF protection show up constantly too. These aren't edge cases in vibe-coded apps.
// AI often generates vulnerable auth patterns
app.post('/login', async (req, res) => {
const user = await db.user.findFirst({
where: {
email: req.body.email,
password: req.body.password // Plain text comparison!
}
});
if (user) {
res.cookie('userId', user.id); // Insecure cookie!
res.json({ success: true });
}
});
Better Approach: Use Auth Providers
In most cases, an established auth service beats building your own:
| Provider | Best For | Features |
|---|---|---|
| Clerk | Next.js apps, fast setup | Pre-built components, MFA, social login |
| Auth0 | Enterprise, complex requirements | SSO, compliance features, custom rules |
| Supabase Auth | Full-stack with Supabase | Row-level security integration, social login |
| NextAuth.js | Self-hosted, flexible | Many providers, database sessions |
Auth providers handle: Password hashing, rate limiting, secure sessions, MFA, and password reset flows. All the things that are easy to get slightly wrong. You focus on your app.
What is broken authentication?
Broken authentication refers to weaknesses in login and session management that let attackers compromise passwords, keys, or session tokens. This can lead to account takeover, unauthorized access, or identity impersonation.
Should I build my own authentication system?
For most apps, no. Established providers like Clerk, Auth0, or NextAuth handle password hashing, session management, and brute-force protection. Rolling your own is only worth it if you have specific constraints those services can't meet.
Is storing passwords in plain text really that bad?
Yes, it's critical. If your database is breached, attackers get everyone's passwords immediately. Since users reuse passwords, this often leads to account compromises on other sites. Always hash passwords with bcrypt, argon2, or scrypt.
What's the difference between authentication and authorization?
Authentication verifies who you are (login). Authorization determines what you can do (permissions). Both are important: broken authentication lets attackers log in; broken authorization lets logged-in users access things they shouldn't.
Check Your Authentication Security
Our scanner tests your login system for common vulnerabilities.