Skip to content

feat(oauth): return returnTo from the callback and restrict its scheme - #63

Merged
Bccorb merged 1 commit into
mainfrom
feat/oauth-return-to-in-callback
Sep 7, 2026
Merged

feat(oauth): return returnTo from the callback and restrict its scheme#63
Bccorb merged 1 commit into
mainfrom
feat/oauth-return-to-in-callback

Conversation

@Bccorb

@Bccorb Bccorb commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Closes the last finding from the second seamless-auth-api review, which could not be fixed
in that repo because both OAuth schemas live here and are z.core.$strip: adding the field
there would have been silently discarded by response validation.

returnTo was a field that looked supported and did nothing

StartOAuthLoginRequestSchema has taken a returnTo since OAuth landed. seamless-auth-api
validates it against the configured origins and signs it into the state, and
seamless-auth-react sends it. No schema carried it to the end of the flow, so a client that
asked to be returned somewhere had no way to learn where, and the API's validation of it
changed nothing observable.

OAuthLoginSuccessResponseSchema now carries an optional returnTo. The API reads it back
out of the signed state rather than from the callback request, so it is the value accepted at
/start and not one introduced at the end of the round trip. It is absent when the caller
asked for nothing, so a client falls through to its own default rather than treating absence
as an error.

Both returnTo fields move to RedirectTargetSchema

z.url() accepts javascript:alert(1) and data:text/html,.... A client navigates to
whatever comes back out of this flow, so returnTo is the same sink a magic link destination
is, and that is exactly what RedirectTargetSchema was added for in 0.18.0. The OAuth fields
should have used it then.

Worth noting the API is not currently exposed by this: allowedReturnTo parses the value and
compares its origin against the configured ones, and a javascript: URL has no matching
origin, so it was already dropped. This moves the refusal to the schema, where it is stated
once rather than depending on a downstream origin check to fall the right way.

Compatibility

Additive for any consumer that ignores the new field. The one behaviour change is that a
caller sending a javascript: or data: returnTo is now refused at the schema instead of
being silently dropped later, which is the point.

Minor rather than major, per the repo's pre-1.0 convention.

Follow-up in the API

seamless-auth-api populates the field once this is published. That change is small
(finishOAuthLogin already holds statePayload), but it cannot land until the dependency is
bumped, so it follows separately rather than being written against an unpublished schema.

Checks

npm run typecheck, npm run lint, npm run format:check clean. 233 tests pass, up from
228: this adds the first test file for the OAuth schemas, covering the round trip, the absent
case, and both refused schemes.

StartOAuthLoginRequestSchema has taken a returnTo since OAuth landed and nothing
ever gave it back. seamless-auth-api validated it against the configured origins
and signed it into the state, and seamless-auth-react sent it, but no schema
carried it to the end of the flow, so a client that asked to be returned
somewhere had no way to learn where. It was a field that looked supported and did
nothing.

OAuthLoginSuccessResponseSchema now carries an optional returnTo, read back out
of the signed state rather than from the callback request, so it is the value
validated at /start and not one introduced at the end of the round trip.

Both returnTo fields move from z.url() to RedirectTargetSchema. z.url() accepts
javascript:alert(1) and data:text/html,..., and a client navigates to whatever
comes back out of this flow, so it is the same sink a magic link destination is.
That is what RedirectTargetSchema was added for in 0.18.0.
@Bccorb
Bccorb merged commit 9541d01 into main Sep 7, 2026
1 check passed
@Bccorb
Bccorb deleted the feat/oauth-return-to-in-callback branch September 7, 2026 21:57
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