Skip to content

fix(core): keep ephemeral tokens out of cookie-transport bodies - #206

Merged
Bccorb merged 1 commit into
mainfrom
fix/cookie-transport-ephemeral-tokens
Oct 8, 2026
Merged

Bccorb merged 1 commit into
mainfrom
fix/cookie-transport-ephemeral-tokens

Conversation

@Bccorb

@Bccorb Bccorb commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Closes #202.

Why

Under cookie transport the adapter keeps every auth token in an httpOnly cookie so page scripts never see one. Two flows broke that by returning the upstream body unchanged:

  • OTP sends. POST /otp/generate-email-otp, /otp/generate-phone-otp, /otp/generate-login-email-otp and /otp/generate-login-phone-otp returned the ephemeral token the auth API re-mints on every send.
  • Registration. POST /registration/register stored the registration token in its cookie and also returned it in the body.

Any script on the adopter's origin could read those tokens, and so could anything that captures bodies (devtools exports, APM payload capture, service workers, proxy logs). They are pre-auth tokens, not sessions, so the impact is limited, but it breaks the guarantee the cookie is there to give.

What changes

  • New helper. withoutTokens in core removes top-level token and refreshToken. The manifest proxy from feat(core): serve auth API routes from the adapter manifest #203 now uses it too, so both paths strip the same way.
  • registerHandler. Strips the body in cookie transport.
  • requestOtpHandler. Takes an optional transport and strips unless it is bearer. Express, Fastify and Next.js all pass it. A caller that omits it gets the safe default.
  • Bearer transport is unchanged. The client holds its own tokens there.
  • Delivery payloads. delivery.token (the OTP code) is nested, so it is left for applyExternalDelivery to send and strip as before.

I checked the auth API: these are the only flows that return a token through an adapter's unchanged-body path. Magic-link request and verify, the OAuth start routes, and the admin, session and system-config passthroughs return no token.

Downstream

Nothing reads these tokens through an adapter's cookie transport:

  • The CLI calls the auth API directly with a Bearer token.
  • @seamless-auth/client reads no body tokens in cookie mode. In bearer mode it reads them from register, which still returns them.
  • The verify harness reads them only from direct API calls.

Checks

  • pnpm test: core 401, express 210, fastify 156, nextjs 165, all passing.
  • pnpm check:types-current passes.
  • New tests:
    • Core: both handlers in each transport, plus the delivery payload surviving.
    • Parity: Fastify and Next.js are held to the same bodies as Express on all four OTP sends and register, the same registration cookie, and the token still reaching a bearer client.

Patch changeset for all four packages.

Under cookie transport the OTP send routes returned the ephemeral token the
auth API re-mints on every send, and registration returned the token it had
just stored in the registration cookie. Page scripts could read both, which
is what the httpOnly cookie exists to prevent.

Both now drop token and refreshToken from the body in cookie transport,
through one helper the manifest proxy also uses. requestOtpHandler takes a
transport option, which every adapter passes; without it the token is
dropped. Bearer transport is unchanged.

Closes #202.
@Bccorb
Bccorb merged commit 5274afe into main Oct 8, 2026
4 checks passed
@Bccorb
Bccorb deleted the fix/cookie-transport-ephemeral-tokens branch October 8, 2026 12:28
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.

fix(core): cookie transport returns ephemeral tokens in response bodies

1 participant