Skip to content

Login email flow uses deprecated fetchSignInMethodsForEmail; Email Enumeration Protection toggle locks out all email/password users #692

@bpowers

Description

@bpowers

Description

The email login flow in src/app/Login.tsx is built on Firebase's deprecated fetchSignInMethodsForEmail API and has a console-toggle failure mode that can silently lock out every email/password user.

onSubmitEmail (src/app/Login.tsx:133-151) calls fetchSignInMethodsForEmail(this.props.auth, email) and branches on the returned methods:

  • methods.includes('password') -> show the password card (showPassword)
  • methods.length === 0 -> show the signup card (showSignup, "Create account")
  • google.com / apple.com -> provider redirect card (showProviderRedirect)

Two problems:

  1. Deprecated API. fetchSignInMethodsForEmail is deprecated by Firebase. It was deprecated specifically because it enables email enumeration.

  2. Console-state lockout. If Email Enumeration Protection is enabled on the Firebase project, fetchSignInMethodsForEmail returns [] for every email, regardless of whether an account exists. Enumeration Protection is default-ON for Firebase projects created after 2023-09-15. This project predates that date, so it is currently default-OFF -- but it is a single toggle in the Firebase console (and a setting Firebase actively recommends turning on).

    The moment that toggle is flipped:

    • Every existing email/password user hits the methods.length === 0 branch and is routed to the "Create account" signup card.
    • On submit, createUserWithEmailAndPassword (Login.tsx:188) throws auth/email-already-in-use, whose raw message is surfaced via this.setState({ passwordError: err.message }) at Login.tsx:193.
    • The user can never reach the password card (showPassword) and therefore can never reach the "Trouble signing in?" recovery link, which is only rendered inside the password card (Login.tsx:298-300).

    Net effect: enabling a Firebase-recommended security setting silently and completely locks out all email/password users, with no in-UI path to recovery.

Secondary issue discovered in the same flow: the recovery and signup email-send promises ignore their outcome. onSubmitRecovery (Login.tsx:153-167) awaits sendPasswordResetEmail and unconditionally transitions to the password card with no success confirmation, and a send failure (rejected promise) produces no error UX. (onSubmitNewUser/createUserWithEmailAndPassword does surface errors, but the password-reset path does not.)

Why it matters

  • Correctness / availability (deploy risk): A one-click console change that Firebase recommends turns into a total lockout of email/password users. There is no code-level guard or warning, and no in-app recovery path once the misrouting happens. This is precisely the kind of latent footgun that bites during a routine security-hardening pass.
  • Maintainability: The flow depends on a deprecated API that Firebase may remove, and its correctness is coupled to a remote console setting that is invisible from the codebase.
  • UX: Silent failure on the password-reset send; users get no feedback that a reset email was (or was not) sent.

Component(s) affected

  • src/app -- src/app/Login.tsx (onSubmitEmail, onSubmitNewUser, onSubmitRecovery, and the showPassword/showSignup cards)

Possible approaches (do not implement as part of this issue)

The suggested fix is to drop fetchSignInMethodsForEmail entirely and make the flow enumeration-protection-compatible:

  1. From the email card, present the password field directly (single combined sign-in card) rather than pre-fetching sign-in methods to decide which card to show.
  2. Attempt signInWithEmailAndPassword, and branch on the error code instead of pre-fetched methods:
    • auth/invalid-credential (Enumeration-Protection-era unified error for wrong-password / no-such-user) -> show inline error plus the "Trouble signing in?" / create-account affordances directly on the card.
    • auth/user-not-found (when protection is OFF) -> offer the signup path.
  3. Surface the OAuth-provider case via the existing provider buttons rather than auto-routing from fetchSignInMethodsForEmail.
  4. Make sendPasswordResetEmail and account creation report success/failure explicitly (await the promise, show a confirmation or error).

This restructuring would also make it safe to turn Email Enumeration Protection ON, which is the more secure configuration.

Relationship to existing issues

Context

Identified during a deploy-risk audit of the login flow (confirmed by independent verifiers); the current Login.tsx was read end-to-end to confirm the line references and behavior described above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendInvolves the Google App Engine node appsecuritySecurity, auth hardening

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions