Skip to content

fix(oauth): bind the OAuth state to the client that started sign-in - #465

Merged
Bccorb merged 3 commits into
mainfrom
fix/oauth-state-binding
Oct 21, 2026
Merged

Bccorb merged 3 commits into
mainfrom
fix/oauth-state-binding

Conversation

@Bccorb

@Bccorb Bccorb commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Closes #463.

What

  • POST /oauth/:providerId/start returns a random binding (32 bytes, base64url) next to state. Only its SHA-256 goes into the signed state; it is never put in the authorization URL.
  • POST /oauth/:providerId/callback takes binding and refuses a state whose hash it does not match (constant-time), before the replay cache records the state and before the provider is contacted. A wrong binding therefore cannot use up a state its rightful client has yet to finish.
  • Refusals log oauth_login_failed with state_binding_missing or state_binding_mismatch (was a single invalid_state), so an operator can see old clients during the upgrade.
  • OAUTH_ALLOW_UNBOUND_STATE=true accepts a callback with no binding, warns at boot, and is removed in v0.20. A binding that is sent is always checked.
  • docs/oauth.md and docs/configuration.md describe the binding and the escape hatch.

Contract change

Additive in the schemas, but by default it refuses OAuth callbacks from clients that do not send binding. Patch changeset by maintainer decision (v0.19 ships as 0.19.x); the changeset body states the break.

Ripple:

  • @seamless-auth/types (feat(oauth): add the state binding to the start response and callback request seamless-auth-types#104) adds the field. This PR needs that release: with types 0.28.0 the request schema strips binding, so CI fails until the dependency is bumped. Draft until then.
  • @seamless-auth/client / react / react-native: keep binding from startOAuthLogin and send it from finishOAuthLogin (PR to follow).
  • Server adapters (express, fastify, nextjs, Go, Python, Rust) pass both bodies through, so no code change.
  • seamless-cli verify harness posts callbacks directly and needs the field.

Checks (local, against the unreleased types build)

  • typecheck, lint, format:check clean; build ok
  • npm run test:run: 138 files, 1891 passed, 1 todo
  • npm run coverage: all files 98.93 statements / 95.43 branches / 99.29 functions / 99.16 lines

Comment thread src/services/oauthService.ts Dismissed
Comment thread tests/unit/services/oauthService.spec.ts Fixed
@Bccorb
Bccorb marked this pull request as ready for review October 21, 2026 01:44
@Bccorb
Bccorb merged commit 9e2914b into main Oct 21, 2026
7 of 8 checks passed
@Bccorb Bccorb mentioned this pull request Oct 21, 2026
5 of 35 tasks
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(oauth): bind the OAuth state to the client that started sign-in

2 participants