Prevent Account Enumeration in Login Error Messages

A login endpoint becomes an account-discovery tool when it says “user not found” for one email and “wrong password” for another. Return the same public response for every authentication failure.

Last updated: October 16, 2026.

$account = findAccountByEmail($email);
$hash = $account?->passwordHash ?? $dummyPasswordHash;
$passwordMatches = password_verify($password, $hash);

if (!$account || !$passwordMatches || !$account->isActive) {
    http_response_code(401);
    echo 'Invalid email or password.';
    exit;
}

startAuthenticatedSession($account);

Generate and store $dummyPasswordHash in configuration. Verifying against it when no account exists helps keep the expensive password-hash step present in both paths.

Keep the observable response consistent

The OWASP Authentication Cheat Sheet recommends generic responses for an unknown account, incorrect password, locked account, or disabled account. Keep the HTTP status, response body, redirects, and visible workflow consistent as well.

Timing is another signal. Look up the normalized identifier and perform one password verification even when no record exists. Avoid a fast early return for unknown users. Exact timing equality is unrealistic, but the code paths should perform comparable work.

Preserve useful detail privately

Generic public errors do not mean discarding diagnostics. Log the internal reason with a request identifier, source context, and rate-limit decision, while avoiding passwords, raw reset tokens, and unnecessary personal data. Restrict access to those logs and define retention.

Apply throttling by account and network signals, alert on enumeration patterns, and require stronger checks for recovery and sensitive actions. Do not disclose a lockout before authentication; otherwise the lock message confirms that the account exists. Notify the account owner through a separate trusted channel when that is appropriate.

Password-reset and registration forms need the same review. A reset form can always say that instructions will be sent if the address is registered. For registration, consider a neutral continuation flow or send the existing owner a notice without revealing account state to the requester. Continue with modern SaaS authentication, blocking repeated login attempts, and secure authentication cookies.

Related Web Cheat Sheet guides

Sergey Kornilov

Sergey Kornilov