labstack / labstack/fanout

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

Open Beginner friendly
#220 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
HTML
Stars
4
Forks
0
Avg merge
4h 4m
Merged PRs (30d)
57

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in ui/host/src/auth.tsx, especially the codeSent state at line 91 and the conditional verification-code field around line 222. Trace the login form's send and verify flow, then run the documented reproduction with a reload or discarded tab. Done means an outstanding address and unexpired code remain usable after remounting, while expired pending state is not restored.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
authentication, frontend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.