Skip to content

feat(magic-link): let a caller choose where the link lands - #138

Merged
Bccorb merged 1 commit into
mainfrom
feat/magic-link-redirect-target
Aug 31, 2026
Merged

feat(magic-link): let a caller choose where the link lands#138
Bccorb merged 1 commit into
mainfrom
feat/magic-link-redirect-target

Conversation

@Bccorb

@Bccorb Bccorb commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Last of three. fells-code/seamless-auth-api#240 added redirectUri to the magic link request, fells-code/seamless-auth-server#151 taught the adapter to forward it, and nothing in this SDK could supply one, so an end-user app still had a single destination for every magic link.

What changed

requestMagicLink takes an optional redirectUri, carried in the request body the client already sends:

await authClient.requestMagicLink('https://app.example.com/magic');

Additive. Omit it and the same empty body goes out, so the default path is byte for byte unchanged and no caller has to do anything. Against an older adapter or API the field is dropped and the link keeps the deployment's destination.

Not validated here

The deployment owns the allowlist. It checks the value against its configured origins and answers 400, which surfaces as an ordinary error result. An allowlist kept in two places is one that eventually disagrees with itself, so the client reports the refusal rather than guessing at it. There is a test for that.

A comment that had gone stale

The empty body carried this:

Sends an empty JSON body on purpose. The adapter ignores it, but it makes fetchWithAuth declare a JSON content type, which forces a CORS preflight.

"The adapter ignores it" stopped being true the moment the adapter started reading redirectUri. Reworded to explain only the part that still holds, which is the preflight, since that is the load-bearing reason the body exists at all (a bodyless POST is a simple request, and a GET here was CSRF-able).

Bundled views left alone

Login.tsx and MagicLinkSent.tsx still call requestMagicLink() with no argument, so their behaviour is unchanged. A destination is app configuration rather than something a user picks in a form, and the headless client path is where it belongs. Worth revisiting if the bundled views ever need to target a mobile build.

Verification

npm run typecheck, npm run lint, npm run format:check, npm run build all clean. 317 tests passing across 32 suites, 2 of them new. Verified non-vacuous: reverting the client to always send {} fails the new forwarding test.

Order

The other two are merged, so this completes the chain and the feature works end to end once this is released alongside them.

seamless-auth-api takes a redirectUri on the magic link request and the server
adapter forwards it, but nothing in this SDK could supply one, so a deployment
serving a web app and a mobile app still had a single destination for every link.

requestMagicLink takes an optional redirectUri, carried in the body the client
already sends. Omitting it sends the same empty body as before, so the default
path is byte for byte unchanged.

Not validated here. The deployment checks the value against its configured
origins and refuses anything else, and an allowlist kept in two places is one
that eventually disagrees with itself, so a refusal is reported as an ordinary
error result rather than pre-empted.

The comment on the empty body said the adapter ignores it. That stopped being
true when the adapter started reading redirectUri, so it now explains only the
part that still holds, which is that sending a body forces the CORS preflight.
@Bccorb
Bccorb merged commit 17a657d into main Aug 31, 2026
3 checks passed
@Bccorb
Bccorb deleted the feat/magic-link-redirect-target branch August 31, 2026 15: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.

1 participant