Skip to content

iPhone sign-in takes a pasted code, and a lost refresh no longer signs a device out - #132

Merged
NiallJoeMaher merged 1 commit into
mainfrom
signin-paste-and-refresh
Nov 3, 2026
Merged

NiallJoeMaher merged 1 commit into
mainfrom
signin-paste-and-refresh

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

Paste. The code field had maxLength={6}. iOS rejects a paste that is longer than that, and a code copied from Mail usually comes with a trailing space or newline, so the field stayed empty. I dropped maxLength. onChangeText already strips non-digits and keeps the first six.

Signed out. Each refresh token works once. Sometimes a refresh reaches the server but its answer never reaches the device: the share sheet closes, or a Vercel cold start runs past the share extension's 5 s timeout. The device then keeps the spent token. The next time it uses that token, which can be hours or days later, Better Auth sees a reuse and deletes every token the app holds. The device is signed out.

  • refreshTokenReuseInterval: 30 * DAY (Better Auth's MCP default is 30 s). A spent token now gets the replayed answer for 30 days. After that, reuse still kills the token family.
  • A replay that arrives late comes back with expires_in: 0. The shared session (packages/client) and the Swift share extension now refresh again with the replayed refresh token, instead of sending a dead access token that gets a 401, which also signs out.
  • Trade-off: within that window, a stolen spent refresh token gets the current pair rather than tripping reuse detection.
  • docs/mcp.md is updated.

Tests

  • signin-code.test.ts: refresh, lose the answer, then a day later replay the spent token. It gets the same new token, which still refreshes. The test fails with the old 30 s window.
  • client.test.ts: a replay that comes back expired refreshes again.
  • Local typecheck, lint and 504 unit tests pass. e2e was not run locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Per4f8yL5A7nL9frZVDJj7

…s a device out

The code field had maxLength 6, and iOS drops a whole paste longer than that,
so a code copied from Mail with a trailing space or newline never arrived.
The field already keeps only the first six digits.

A refresh token works once. When a refresh reached the server but its answer
never reached the device (the share sheet closed, a cold start outlasted the
5 second timeout), the device kept the spent token, and using it later made
Better Auth delete every token the app had: signed out. The server now replays
the answer to a spent token for 30 days instead of 30 seconds, and the app and
share extension refresh again when that replay comes back already expired.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Per4f8yL5A7nL9frZVDJj7
Signed-off-by: NiallJoeMaher <nialljoemaher@gmail.com>
@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
pluck Ready Ready Preview Nov 3, 2026 4:32pm UTC

Request Review

@NiallJoeMaher
NiallJoeMaher merged commit 3b8d07d into main Nov 3, 2026
5 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the signin-paste-and-refresh branch November 3, 2026 16:37
NiallJoeMaher added a commit that referenced this pull request Nov 3, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Per4f8yL5A7nL9frZVDJj7
Signed-off-by: NiallJoeMaher <nialljoemaher@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant