Skip to content

fix(client)!: request magic links over POST#97

Merged
Bccorb merged 1 commit into
mainfrom
fix/magic-link-post
Jul 20, 2026
Merged

fix(client)!: request magic links over POST#97
Bccorb merged 1 commit into
mainfrom
fix/magic-link-post

Conversation

@Bccorb

@Bccorb Bccorb commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Closes #93

Follow-on to fells-code/seamless-auth-server#103, which removed GET /auth/magic-link from the Express adapter and replaced it with POST /auth/magic-link.

GET /auth/magic-link was a state-changing route reachable as a simple cross-site request. With the SameSite=None cookie default and no CSRF or Origin check, an <img src="https://api.example.com/auth/magic-link"> on any page triggered unbounded magic-link emails to a signed-in user: mailbox flooding, sender-reputation damage, and phishing cover.

Change

requestMagicLink in src/client/createSeamlessAuthClient.ts now issues a POST.

It also sends an empty JSON body, which is worth calling out because it is not obvious. fetchWithAuth only declares Content-Type: application/json when a body is actually present (src/fetchWithAuth.ts). A bodyless POST therefore sends no content type, which is still a CORS simple request: no preflight, so it stays reachable cross-site via fetch(url, { method: 'POST', credentials: 'include', mode: 'no-cors' }). Flipping the verb alone would have looked like a fix without being one. The JSON content type is what forces the preflight and actually closes the vector.

The adapter ignores the body (identity comes from the pre-auth cookie), and every other POST in this client already sends a JSON body, so adopter CORS configs already permit the header.

Not changed

Audited the rest of the client:

  • logout and logoutAllSessions already use DELETE /logout and DELETE /logout/all. The adapter's removal of GET /logout does not affect them.
  • checkMagicLink (GET /magic-link/check) is unchanged in the adapter.
  • verifyMagicLink (GET /magic-link/verify/:token) stays GET on purpose. The path token is the credential and it arrives from an email link.

Tests

Added a case asserting requestMagicLink calls /magic-link with method: 'POST' and the JSON body. No existing test pinned the verb, which is why the SDK could have drifted from the adapter unnoticed.

Verified the test is not vacuous: reverting the client to method: 'GET' fails it while the other 27 suites still pass.

Verification

npm run typecheck    clean
npm run lint         clean
npm run format:check all matched files use Prettier code style
npm test             28 suites / 209 tests passed

Release ordering

Breaking against older adapters: requestMagicLink gets a 404 unless the adapter serves the POST route. fells-code/seamless-auth-server#103 must land and publish before this releases. Changeset is minor, matching the 0.x breaking channel.

Downstream consumers (seamless-portal, seamless-review-web, seamless-templates) all call authClient.requestMagicLink() rather than hitting the route directly, so they need only a version bump, no code change.

Unrelated note

package-lock.json on main carries a stale "version": "0.1.1" while package.json is at 0.4.0. Any npm install silently rewrites it. Kept out of this PR to stay focused; worth a separate chore commit.

@Bccorb
Bccorb force-pushed the fix/magic-link-post branch 2 times, most recently from 711987c to cd2d334 Compare July 20, 2026 14:21
GET /auth/magic-link was a state-changing route reachable as a simple
cross-site request, so an img tag on any page could trigger unbounded
magic-link emails to a signed-in user. The Express adapter removed the
GET route in favor of POST.

The request carries an empty JSON body. The adapter ignores it, but the
resulting JSON content type forces a CORS preflight, which is what keeps
the route from being reachable cross-site. A bodyless POST would still
be a simple request and would leave the vector open.

BREAKING CHANGE: requires an adapter serving POST /auth/magic-link
(@seamless-auth/express 0.9.0 or later). Against an older adapter,
requestMagicLink gets a 404. Callers need no code change.

Refs #93
@Bccorb
Bccorb force-pushed the fix/magic-link-post branch from cd2d334 to a5bf59c Compare July 20, 2026 14:23
@Bccorb
Bccorb merged commit 02c99f3 into main Jul 20, 2026
2 of 3 checks passed
@Bccorb
Bccorb deleted the fix/magic-link-post branch July 20, 2026 14:26
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.

requestMagicLink must use POST after the express adapter drops GET /magic-link

1 participant