Every login, sign-up and password reset form answers a question an attacker should never get answered: does this account exist? A list of confirmed usernames or email addresses turns credential stuffing and password spraying into smaller, quieter and more successful attacks, and in some services the mere fact that someone has an account is sensitive. This guide covers where account existence leaks (messages, status codes, redirects, response size and timing, identifier-first and SSO flows, APIs), how to design each flow so it gives nothing away, when the usability cost is not worth paying, and how to test and monitor for leaks. It is for product security engineers, identity architects and developers who own customer or workforce sign-in. The reset request itself is covered in the Forgot Password Flow Guide; this page covers every other surface.
Key takeaways
- Enumeration is reconnaissance for account takeover. MITRE ATT&CK notes that adversaries probe authentication services to reveal valid usernames (T1589 Gather Victim Identity Information), then use them for password spraying and credential stuffing.
- Messages are the easy part. Status codes, redirect targets, response length, cookies and timing leak just as reliably as "user not found", and all of them must match.
- Sign up is the hardest flow. The pattern that works is to always accept the address and tell the owner by email, so only the mailbox holder learns that an account already exists.
- Enumeration resistance has a cost. Public usernames, social handles and some B2B SSO flows cannot hide existence; there, rely on rate limits, bot controls and MFA instead of pretending to hide it.
- Test with pairs. For every flow, send a known account and an unknown one and diff everything: body, headers, status, redirects and response time distribution.
Where account existence leaks
OWASP Web Security Testing Guide test WSTG-IDNT-04 (Testing for Account Enumeration and Guessable User Account) lists the signals testers look for: different messages and response lengths, error codes, error parameters in URLs and redirect targets, page titles, HTTP status differences such as 403 for an existing user and 404 for a missing one, recovery messages, and response timing. CWE-204 Observable Response Discrepancy describes the underlying weakness. The table maps those signals to the flows where they usually appear.
| Surface | Typical leak | Design that does not leak |
|---|---|---|
| Login (username and password) | "No account with that email" versus "Wrong password"; 404 versus 401; faster response for unknown users | One message ("Invalid username or password"), one status code, the same processing path for both cases |
| Identifier-first login | Password page shown only for known users; unknown users sent to sign up | Always show the next authentication step; decide only after the credential is checked |
| Sign up / registration | "This email is already registered" | Always show "Check your email"; send a "you already have an account" email to existing owners |
| Password reset request | "Email not found"; slower response when an email is actually sent | Generic confirmation; queue the email so every request returns at the same speed |
| Change email or add email | "Address already in use" when a signed-in user changes address | Send verification to the new address; tell the existing owner by email if it is taken |
| Account lockout and disabled accounts | "Your account is locked" shown only for real accounts | Same generic message for locked, disabled and missing accounts; reach the user through their registered channel |
| MFA and OTP steps | OTP prompt or "code sent to ***-123" only for real accounts; masked phone or MFA method shown before authentication | Do not reveal enrolled methods or masked contact details before the first factor succeeds |
| Profiles, invitations, sharing | Public profile URLs, "invite user" or "share with" fields that confirm membership | Require authentication and authorisation for lookups; rate-limit search and invite endpoints |
| APIs and mobile apps | Distinct JSON error codes (USER_NOT_FOUND), availability-check endpoints | Same error object for all failures; rate-limited availability checks only where existence is public anyway |
ATT&CK's T1589 entry specifically mentions responses that reveal "permitted MFA methods associated with those usernames". Showing the masked phone number or enrolled factor before the password is verified is enumeration plus reconnaissance in one step.
Making responses identical
OWASP ASVS 5.0 requirement V6.3.8 states the target precisely: valid users must not be deducible from failed authentication challenges, "such as by basing on error messages, HTTP response codes, or different response times", and registration and forgot password functionality must have the same protection. Treat every failure response as a fingerprint and make the fingerprints match.
- Message: one string for all failures. The OWASP Authentication Cheat Sheet gives "Login failed; Invalid user ID or password" for login and a conditional "if that address is in our database" wording for recovery.
- Status code: the cheat sheet warns that a generic page with different codes (for example 200 versus 403) still leaks. Use the same code for every failure.
- Body and length: no hidden fields, CSRF token differences, user IDs, display names or avatars in the failure body. Compare byte lengths in tests.
- Redirects and URLs: the same redirect target and query parameters for all failures; no
?error=nouser. - Headers and cookies: do not set a session, device or "remember username" cookie only for existing accounts.
- Timing: see below; it is the leak that survives most code reviews.
Removing timing differences
The OWASP Authentication Cheat Sheet describes the "quick exit" problem: code that returns as soon as the username is not found answers faster than code that goes on to verify a password hash. With a deliberately slow hash such as Argon2id, scrypt or bcrypt, the difference is easy to measure remotely. The fix is to run the same work on both paths:
- Dummy verification: when the account does not exist, verify the submitted password against a fixed dummy hash created with the same algorithm and parameters as real hashes. Regenerate it when hash parameters change, or the timing gap returns.
- Asynchronous side effects: sending email or SMS, writing audit rows and calling risk engines should be queued, and the response returned before they run, for both real and unknown accounts.
- Same lookups: avoid extra database or directory calls that only happen for real accounts (loading the profile, checking lockout state) before the response is decided; do them after, or for both paths.
- Do not rely on random delays alone: random sleep adds noise, but with enough samples the averages still separate. Equal work is the control; jitter is a supplement.
Sign up without disclosure
Registration is where enumeration resistance most often gets dropped, because "this email is already in use" feels helpful. The pattern that keeps both security and usability is to move the answer into the mailbox:
- The form accepts any well-formed address and always responds "Check your email to continue".
- If the address is new, the email contains a verification link that completes registration.
- If the address already has an account, the email says so and offers sign-in and password reset links. The account is not changed.
- The response time and content of step 1 are the same in both cases, with emails queued.
The OWASP cheat sheet gives the same approach for account creation: tell the user that an activation link has been emailed rather than that the ID is in use. For sign-up forms that also create a password, do not set the password until the address is verified, or an attacker can pre-register a victim's address. Combine this with abuse controls from the Fake Account Creation and Sign-up Fraud Guide, because "always accept" also accepts bots.
Where users choose public usernames or handles, existence is public by design: the handle appears on posts and profile URLs. Do not pretend otherwise. Offer an availability check, rate-limit it per session and source, require a bot challenge after a threshold, and make sure the handle is not also the login identifier, so knowing it does not help a password attack.
Identifier-first, passkeys and SSO
Modern sign-in often asks for the identifier first, then decides what to show: a password field, a passkey prompt, or a redirect to the user's identity provider. Each branch can leak.
- Password step: show the password step for every identifier, including unknown ones, and fail only after submission. The OWASP Credential Stuffing Prevention Cheat Sheet notes that multi-step login can add friction for attack tools but must not facilitate enumeration.
- Passkeys: prefer passkey autofill with discoverable credentials, where the browser offers the user's passkeys without the site first looking up the username. If a username-first passkey flow is used, avoid returning a credential list only for existing accounts. The W3C Web Authentication Level 3 Recommendation includes a privacy consideration on username enumeration for relying parties (section 14.6.2). See Passkey Autofill and Conditional UI.
- SSO and home realm discovery: routing by email domain reveals that a domain is federated, which is usually acceptable, but it should not reveal whether the individual user exists. Route on the domain only and let the identity provider handle the user. See Home realm discovery in Entra ID and federation.
- Magic links and email OTP: always respond "If an account exists, we have sent a code", and keep code entry screens identical. See Magic Links and Email OTP Security Guide.
When to accept some disclosure
The OWASP Authentication Cheat Sheet is explicit that generic messages have a usability cost and that the choice depends on how critical the application is. Make the decision per flow and write it down.
| Context | Recommended stance | Compensating controls |
|---|---|---|
| Sensitive services (health, finance, dating, legal, children's services), where account existence is itself personal data | Full enumeration resistance on every flow, including sign up and change email | Email-based disclosure only to the mailbox owner; support routes for confused users |
| General consumer services | Resistance on login, reset and lockout; sign up via the mailbox pattern | Rate limits and bot challenges on all identity endpoints |
| Social platforms with public handles | Accept that handles are public; keep login identifier (email or phone) private | Rate-limited availability checks, MFA, breached password screening |
| Workforce sign-in with SSO | Domain-level routing acceptable; no user-level signals before authentication | Identity provider smart lockout and password spraying detection |
| B2B products with invitations | Membership visible to authenticated admins of that tenant only | Authorisation on invite and search APIs; per-tenant rate limits |
Small teams should at minimum fix login, reset and lockout messages and timing, which are cheap. Large consumer platforms usually need the full set plus a central identity API that all clients (web, mobile, partner) use, so a forgotten legacy endpoint does not undo the work.
Rate limits and bot controls
Perfectly uniform responses still allow an attacker to try passwords; imperfect ones (some leaks are unavoidable) are easier to exploit at volume. Rate limiting is the second layer:
- Per account identifier: NIST SP 800-63B (in the SP 800-63-4 series) requires verifiers to limit consecutive failed attempts on a single authenticator to no more than 100, and allows lower limits, bot challenges, increasing delays and risk-based techniques. NIST SP 800-53 control AC-7 Unsuccessful Logon Attempts covers the same ground.
- Per source and globally: spraying spreads attempts across many accounts, so per-account limits alone miss it. Track failures by IP, network, device and connection fingerprint, and watch global failure rates.
- On every identity endpoint: ASVS V2.4.1 asks for anti-automation against excessive calls to application functions; apply it to reset, sign-up, availability checks and invite endpoints, not just login.
- Lockout without disclosure: when a limit triggers, show the same generic failure as any other, and notify the real user through their registered channel.
See the Account Lockout and Brute-Force Protection Guide, the Rate Limiting and API Abuse Guide and CAPTCHA and bot challenges.
Implementation and testing
- Inventory identity surfaces: list every endpoint that takes an identifier: web, mobile, legacy, partner and support tools. Include GraphQL and API endpoints, not just HTML forms.
- Decide the stance per flow: using the table above, record where disclosure is accepted and why.
- Fix messages, codes and redirects: centralise error responses in the identity service so clients cannot add their own.
- Fix timing: add dummy verification and queue side effects; regenerate the dummy hash with every hash parameter change.
- Build paired tests: for each endpoint, automated tests send a known and an unknown identifier and assert identical status, body (after removing nonces), headers, cookies and redirects. Add a timing test that collects a few hundred samples per case in a quiet environment and flags a statistically significant difference.
- Roll out behind a flag: change messages for a pilot cohort first and watch support contacts and sign-up completion; roll back the copy, not the timing fix, if users are confused.
- Retest after changes: include WSTG-IDNT-04 in penetration test scope and rerun the paired tests whenever authentication code, the identity provider or a client changes.
Detection and measurement
Log identity endpoint calls with the outcome, source, device fingerprint and whether the identifier exists (server side only). Enumeration shows up as many distinct identifiers per source, a high share of unknown identifiers, sequential or dictionary-like patterns, and bursts on reset or sign-up endpoints. ASVS V16.3.3 asks for logging of attempts to bypass controls, including anti-automation. See the Secure Logging Vocabulary and Application Security Events Guide.
- Paired test pass rate: share of identity endpoints whose known and unknown responses are indistinguishable; the target is all of them.
- Timing gap: measured median difference between known and unknown identifiers per endpoint.
- Unknown identifier ratio: share of login and reset attempts for identifiers that do not exist; a sudden rise signals enumeration or a stuffing list.
- Distinct identifiers per source: alert when one source, device or fingerprint touches more than a set number of identifiers in a window.
- Support contacts about generic messages: a usability measure that tells you whether the copy needs work.
Common failure patterns
- Fixed login, forgotten sign up: the login page is generic but registration still says "email already registered", which leaks the same fact.
- Same message, different status: the web form is generic while the API it calls returns 404 for unknown users and 401 for wrong passwords.
- Synchronous email: the reset response waits for the email provider only when an account exists, so timing reveals it.
- MFA hints before authentication: masked phone numbers or enrolled factor lists appear after the identifier step.
- Lockout messages: "account locked" appears only for real accounts, and attackers use it to confirm and to deny service.
- Legacy and partner endpoints: an old mobile API version or a partner integration still uses the detailed errors.
Framework mapping
| Framework | Reference | How it applies |
|---|---|---|
| OWASP ASVS 5.0 | V6.3.8; V6.3.1; V2.4.1; V16.3.3 | No deducible valid users in login, registration and reset; anti-automation; logging of bypass attempts |
| OWASP WSTG | WSTG-IDNT-04 Testing for Account Enumeration and Guessable User Account | Test method for every identity surface |
| MITRE CWE | CWE-204 Observable Response Discrepancy (child of CWE-203 Observable Discrepancy) | The weakness class behind enumeration |
| MITRE ATT&CK | T1589 Gather Victim Identity Information; T1589.002 Email Addresses; T1110.003 Password Spraying; T1110.004 Credential Stuffing | Enumeration as reconnaissance and the attacks it enables |
| NIST SP 800-53 Rev. 5 | IA-6 Authentication Feedback; SI-11 Error Handling; AC-7 Unsuccessful Logon Attempts; AU-6 Audit Record Review, Analysis, and Reporting | Obscured feedback, safe errors, attempt limits, monitoring |
| NIST CSF 2.0 | PR.AA-03; DE.AE-02 | Authentication outcomes; analysis of suspicious authentication activity |
| NIST SP 800-63B | Section 3.2.2 rate limiting | At most 100 consecutive failed attempts per authenticator |
Practitioner checklist
- List every endpoint that accepts a username, email or phone number, across web, mobile, API, partner and support tools.
- Return one message, one status code, one redirect and no account-specific cookies for every failure.
- Verify against a dummy hash for unknown accounts and queue all email and SMS sends.
- Use the mailbox pattern for sign up and change email, and do not set credentials before the address is verified.
- Show the next authentication step for every identifier, and never show MFA methods or masked contact details before the first factor succeeds.
- Prefer passkey autofill with discoverable credentials over username-first passkey lookups.
- Rate-limit login, reset, sign up, availability and invite endpoints per account, per source and globally.
- Run paired known and unknown tests, including timing, in CI and in every penetration test.
- Alert on many distinct identifiers per source and on rising unknown identifier ratios.
- Document where disclosure is accepted, such as public handles, and the compensating controls.
Standards and references
- OWASP: Application Security Verification Standard 5.0
- OWASP: Authentication Cheat Sheet
- OWASP: WSTG-IDNT-04 Testing for Account Enumeration and Guessable User Account
- OWASP: Credential Stuffing Prevention Cheat Sheet
- MITRE CWE: CWE-204 Observable Response Discrepancy
- MITRE ATT&CK: Gather Victim Identity Information
- NIST: SP 800-63B Authentication and Authenticator Management
- W3C: Web Authentication Level 3
Related NHI Mgmt Group resources: Forgot Password Flow Guide · Password Spraying and Credential Stuffing Defence Guide · Account Lockout and Brute-Force Protection Guide · Customer IAM (CIAM) Guide · OWASP WSTG Guide