Skip to content

Login loses the pending verification-code state on reload, stranding a valid code #220

Description

@vishr

Summary

The email-code sign-in flow keeps its "a code has been sent" state only in React memory. Any remount — a reload, a restored tab, or returning to a tab the browser discarded while the user was in their mail client — resets the form to its initial state. The verification code field disappears and the user is asked to send a new code.

The code they already received is still valid on the server. There is simply no longer any field to type it into.

Because the code is delivered out of band by email, leaving the page is the normal path through this flow rather than an edge case. On mobile in particular, switching to a mail app and back frequently reloads the tab.

Fanout version

Source build at a1d783f1 (v2026.9.0-1-ga1d783f1), on main.

Reproduction

  1. Run Fanout with auth.mode=local and SMTP configured.
  2. Open the login page, enter a registered email address, and click Send code.
  3. The form switches correctly: the copy reads "Enter the verification code sent to …", a Verification code field appears, and the button becomes Verify code.
  4. Reload the page — or switch to another app and return after the browser has discarded the tab.
  5. The form is back to its initial state: the email field is empty, the Verification code field is gone, and the button reads Send code again.

Expected behavior

Returning to the login page while a code is outstanding should keep the code entry field available, so the code that was already delivered can be used.

Actual behavior

The form resets to the "Send code" state and the delivered code cannot be entered.

The code itself remains valid. Calling the API directly after the reload, with the same address and the code from step 2, succeeds:

POST /api/auth/verify  ->  200 {"status":"authenticated"}
GET  /api/auth/me      ->  200 (session established)

So this is purely lost client state, not a credential or backend problem.

Cause

ui/host/src/auth.tsx:

  • Line 91 — const [codeSent, setCodeSent] = useState(false);
  • Line 222 — the Verification code field renders behind codeSent &&

Nothing persists codeSent or the entered address. There is no sessionStorage, localStorage, cookie, or URL parameter backing the pending-verification state, so every remount starts over.

This is not specific to a local build: the affected chunk (auth-DhIxmh_D.js) is content-hash identical between a local just build and a deployed instance.

Suggested fix

Persist the pending state — the address plus a sent-at timestamp — in sessionStorage, and restore it on mount. Expire the restore after the code's five minute lifetime so a stale tab does not present a field for a code that can no longer work.

Deployment

Source build.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions