Skip to content

feat: add a confirmation step for magic links opened on another device - #189

Merged
Bccorb merged 1 commit into
mainfrom
feat/magic-link-confirm-step
Oct 18, 2026
Merged

Bccorb merged 1 commit into
mainfrom
feat/magic-link-confirm-step

Conversation

@Bccorb

@Bccorb Bccorb commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Client and UI half of the magic link change in seamless-auth-api 0.19.0. A link opened on a device other than the one that asked for it now waits for the reader's approval.

  • @seamless-auth/client
    • client.confirmMagicLink(token): POST /magic-link/verify/{token}/confirm, token encoded into its own path segment, empty JSON body so it takes a CORS preflight.
    • Transport: /magic-link/verify/:token is now preAuth, so bearer mode sends the ephemeral token when it holds one and nothing otherwise. /magic-link/verify/:token/confirm is none. Without that rule it would have fallen through to the default access rule.
    • Flow helpers: getMagicLinkConfirmation(error) (409 plus error: 'confirmation_required', also nested under details; a device class it does not know reads as unknown), isMagicLinkConfirmationRequired, describeMagicLinkDevice, magicLinkConfirmationPrompt (locale formatted time, left out when it does not parse), and confirmMagicLinkSignIn (confirms, then broadcasts on the tab channel in case the requesting tab is in this browser). Types: MagicLinkConfirmation, MagicLinkDeviceClass. Existing exports and MagicLinkOutcome are unchanged.
  • Verify screens (React, Angular, Vue, Svelte): on the 409 they show "Confirm this sign-in", the requesting platform and time, a primary "Yes, sign in that device" button and a secondary "No, this wasn't me" button. Approving shows a role="status" "return to your other device" message and does not refresh or poll a session on this device. A refused confirmation shows a role="alert" and the buttons can be used again. Declining sends nothing and says the email can be ignored. Every other failure still shows "Failed to verify token". Accessible names are the same across bindings.
  • Svelte: a held link is taken back out of the "already spent" set, so a remount can ask again.
  • Docs: the React, client, React Native, Angular, Vue and Svelte READMEs, plus AGENTS.md.
  • Changeset: minor for client, react, angular, vue and svelte. patch for react-native, which only changes its README.

Checks (run locally, the fork runs no Actions)

npm run lint                 -> eslint ./packages, no output (clean)
npm run format:check         -> All matched files use Prettier code style!
npm run typecheck            -> exit 0 (svelte-check: 569 FILES 0 ERRORS 0 WARNINGS)
npm run check:types-current  -> @seamless-auth/types "^0.28.0" is current (latest 0.28.0)
npm test -- --coverage --runInBand
  Jest:   Test Suites: 45 passed, 45 total; Tests: 593 passed, 593 total
  Vitest: Test Files 3 passed (3); Tests 41 passed (41)
npm run build                -> exit 0 (finalize-svelte-dist: index.js is Kit-free; no web storage; cookie transport only)
npm run check-npm-build      -> exit 0
npx commitlint --from HEAD~1 -> clean

Notes

  • @seamless-auth/types 0.28.0 has no schema for the 409 body yet, so MagicLinkConfirmation and MagicLinkDeviceClass live in packages/client/src/flows/magicLink.ts for now. They should become aliases once types publishes the schema.
  • The verify route in the API's adapter manifest is adapter: {}, not credential: 'preAuth'. So the server adapter may not forward the ephemeral bearer in bearer mode unless it handles that separately.

Release order

Part of the v0.19 security release (fells-code/seamless-auth-api#385), reviewed in a private fork. Compatible with the current auth API: the forwarded pre-auth credential and the confirm route are only used by API 0.19.0.

The verify screens in every binding now ask before approving a link opened
on a device other than the one that requested it, and the client gains
confirmMagicLink plus helpers to read the 409 the auth API returns in that
case. Bearer mode sends the ephemeral token on the verify route when it
holds one.
@Bccorb Bccorb added this to the v0.19 milestone Oct 18, 2026
@Bccorb Bccorb added the v0.19 Targets the v0.19 release label Oct 18, 2026
@Bccorb
Bccorb merged commit 59802e2 into main Oct 18, 2026
5 checks passed
@Bccorb
Bccorb deleted the feat/magic-link-confirm-step branch October 18, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v0.19 Targets the v0.19 release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant