Account Enumeration Prevention
Auth Safety · AUTH-24 · Priority: P1
Why It Matters
Account enumeration allows attackers to discover which email addresses have accounts in your app. This information is used for targeted phishing, credential stuffing (testing leaked passwords against confirmed accounts), and social engineering.
AI code generators return different error messages for "email not found" vs "wrong password" — making it trivial for an attacker to build a list of valid accounts by testing email addresses against your login endpoint.
Priority: P1 — Information disclosure that enables targeted attacks.
Affected Stack: Supabase Auth, any auth implementation
The Problem
// ❌ Different errors reveal account existence
const { error } = await supabase.auth.signInWithPassword({ email, password });
if (error?.message === 'Invalid login credentials') {
// This could mean wrong password OR no account
// But Supabase returns this for both — good!
}
// ❌ Password reset reveals account existence
const { error } = await supabase.auth.resetPasswordForEmail(email);
if (error) {
return { error: 'No account found with this email' }; // Reveals existence!
}
return { success: 'Reset email sent' };
The password reset flow is the most common leak — AI tools show "email not found" when the account doesn't exist, confirming to attackers which emails are registered.
The Fix
Return identical responses regardless of whether the account exists.
// ✅ Identical response for password reset
export async function POST(req: Request) {
const { email } = await req.json();
// Fire and forget — don't reveal the result
await supabase.auth.resetPasswordForEmail(email);
// Always return success, even if email doesn't exist
return Response.json({
message: 'If an account exists with this email, a reset link has been sent.'
});
}
// ✅ Registration — same response whether account exists or not
export async function POST(req: Request) {
const { email, password } = await req.json();
const { error } = await supabase.auth.signUp({ email, password });
// Don't distinguish between "already registered" and "signup successful"
return Response.json({
message: 'Check your email to confirm your account.'
});
}
Key rules:
- Login: same error message for "wrong password" and "no account" (Supabase does this by default)
- Registration: same response for new and existing emails
- Password reset: always say "if an account exists, we sent a link"
- Never return user counts, IDs, or existence confirmations in API responses
- Rate limit all auth endpoints to slow down enumeration attempts
References
Related Checks
- Rate Limiting for Auth & Admin — AUTH-09
- Server-Side Auth for Protected Routes — AUTH-04
Is your app safe? Run Free Scan →