Skip to content

feat(auth): redeem magic links in the installed PWA (iOS handoff)#220

Open
kubo6472 wants to merge 2 commits intomainfrom
cursor/pwa-magic-link-handoff-4309
Open

feat(auth): redeem magic links in the installed PWA (iOS handoff)#220
kubo6472 wants to merge 2 commits intomainfrom
cursor/pwa-magic-link-handoff-4309

Conversation

@kubo6472
Copy link
Copy Markdown
Member

@kubo6472 kubo6472 commented May 9, 2026

Summary

iOS does not share cookies or storage between Mobile Safari and a Home Screen web app, so a magic link opened from Mail often logged the user in only in Safari. This change adds a server-backed handoff: on iPhone/iPad outside standalone mode, the raw email token is exchanged for a short-lived code stored in KV; the SPA replaces the URL with /auth/verify?handoff=… so the user can open the same URL in the installed app (or finish in Safari). POST /api/auth/redeem-pwa-handoff issues the normal refresh cookie + JWT in whichever context runs it.

Chromium PWAs can also reuse an existing client for navigations via launch_handler.client_mode: navigate-existing in the web manifest.

API

  • POST /api/auth/magic-pwa-handoff — body { token }; same consume rules as GET /api/auth/verify (including 2FA pending). Full-session path stores { userId } in RATE_LIMIT_KV under auth:pwa-handoff:${code} (10 min TTL) and returns { handoffCode, totpRequired }. If KV is missing, falls back to immediate session (same as GET verify).
  • POST /api/auth/redeem-pwa-handoff — body { code }; one-time redeem → session response + Set-Cookie.

Web

  • pages/auth/verify.vue — iOS non-standalone: POST handoff → navigateTo with handoff query; defer redeem in Safari with copy + “Continue in Safari”; auto-redeem in standalone (display-mode: standalone / navigator.standalone). watch(route.fullPath) so query-only navigations still run the flow.
  • useAuthmagicPwaHandoff, redeemPwaHandoff, exported MagicPwaHandoffResult.
  • utils/strings.ts — copy for the handoff screen.
  • nuxt.config.ts — manifest launch_handler.

Files

  • packages/api/src/auth.ts — shared consumeMagicLinkForUser, issueFullMagicSessionResponse, new handlers.
  • packages/api/src/index.ts — route wiring.
  • packages/web/composables/useAuth.ts, pages/auth/verify.vue, utils/strings.ts, nuxt.config.ts.

Testing

  • npm run typecheck in @vmp/api (pass)
  • npx nuxi prepare in @vmp/web (pass)

Manual verification on a real iPhone (Mail → Safari → Home Screen app) is recommended.

Open in Web Open in Cursor 

- Share consumeMagicLinkForUser between GET verify and new POST magic-pwa-handoff
- Store one-time handoff codes in RATE_LIMIT_KV; redeem issues refresh cookie + JWT
- iOS non-standalone: exchange email token for handoff URL, defer redeem until Safari or PWA
- Manifest launch_handler navigate-existing for Chromium installed apps
- Centralize verify page strings

Co-authored-by: Jakub Doboš <kubo6472@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

Warning

Rate limit exceeded

@kubo6472 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 8 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1ab30971-78db-4b81-b6f8-b58c0772ef7f

📥 Commits

Reviewing files that changed from the base of the PR and between 302167b and 858e0df.

📒 Files selected for processing (6)
  • packages/api/src/auth.ts
  • packages/api/src/index.ts
  • packages/web/composables/useAuth.ts
  • packages/web/nuxt.config.ts
  • packages/web/pages/auth/verify.vue
  • packages/web/utils/strings.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/pwa-magic-link-handoff-4309

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-authored-by: Jakub Doboš <kubo6472@users.noreply.github.com>
@kubo6472 kubo6472 marked this pull request as ready for review May 9, 2026 22:01
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.

2 participants