Skip to content

fix(js): preserve SignUpFuture reference across SSO-to-sign-up transition#9060

Open
FrancoKaddour wants to merge 4 commits into
clerk:mainfrom
FrancoKaddour:fix/signup-future-sso-transfer-stale-ref
Open

fix(js): preserve SignUpFuture reference across SSO-to-sign-up transition#9060
FrancoKaddour wants to merge 4 commits into
clerk:mainfrom
FrancoKaddour:fix/signup-future-sso-transfer-stale-ref

Conversation

@FrancoKaddour

@FrancoKaddour FrancoKaddour commented Jul 1, 2026

Copy link
Copy Markdown

Fixes #8338

When an SSO sign-in transitions to a sign-up flow (e.g. the account does not exist and legal acceptance is required), Client.fromJSON created a brand-new SignUp instance because the existing one had no id while the incoming server data did. The SignUpFuture held by useSignUp() hooks pointed at the stale, id-less instance. Calling signUp.update({ legalAccepted: true }) then sent PATCH /client/sign_ups (no id segment) instead of PATCH /client/sign_ups/{id}, resulting in a 405 error.

Root cause: Client.fromJSON only reuses the existing SignUp instance when this.signUp.id === data.sign_up.id. During SSO transfer the old instance has no id, so the condition was false and a new instance was created — discarding the SignUpFuture reference held by hooks.

Fix: Also update in-place when !this.signUp.id (the instance is still uninitialized). __internal_updateFromJSON populates all fields including id, so the existing SignUpFuture transparently sees the correct id after the transition.

Summary by CodeRabbit

  • Bug Fixes
    • Redirect validation now correctly accepts allowed redirect origins when URLs include non-default ports, including improved handling for wildcard patterns.
    • Redirect checking is more robust against lookalike/disallowed domains and avoids incorrect matches caused by port differences.
    • Sign-up state is preserved across transitions after SSO sign-in, ensuring signUp.update() targets the correct sign-up resource and continues updating reliably.

isAllowedRedirect() compares url.origin against allowedRedirectOrigins
patterns. When the redirect URL includes a non-default port (e.g. :5173
from a Vite dev server), url.origin includes the port suffix, while
patterns like 'https://*.example.net' have none — causing the match to
fail and the redirect to fall back to the home URL silently.

Fix: when url.port is non-empty, also test the port-stripped origin
(protocol + hostname) against each pattern. Domain validation is
preserved — only the port suffix is relaxed.

Fixes clerk#8263
…tion

When an SSO sign-in transitions to a sign-up flow (e.g. account does not
exist, legal acceptance required), Client.fromJSON created a brand-new
SignUp instance because the existing one had no id and the incoming data
did. The old SignUpFuture held by useSignUp() hooks pointed at the stale
id-less instance, so subsequent update() calls sent PATCH to
/client/sign_ups instead of /client/sign_ups/{id} and received a 405.

Fix: update the existing SignUp in-place (via __internal_updateFromJSON)
when it has no id yet, which preserves the SignUpFuture reference and
ensures hooks see the correct id after the transition.

Fixes clerk#8338
@changeset-bot

changeset-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 167447f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@clerk/shared Patch
@clerk/clerk-js Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/electron Patch
@clerk/expo-passkeys Patch
@clerk/expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/headless Patch
@clerk/hono Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/react Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/ui Patch
@clerk/vue Patch
@clerk/swingset Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

@FrancoKaddour is attempting to deploy a commit to the Clerk Production Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 29904de2-e4b1-4848-8460-c0234390fafb

📥 Commits

Reviewing files that changed from the base of the PR and between 9a32372 and 167447f.

📒 Files selected for processing (2)
  • packages/shared/src/internal/clerk-js/__tests__/url.test.ts
  • packages/shared/src/internal/clerk-js/url.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/shared/src/internal/clerk-js/tests/url.test.ts
  • packages/shared/src/internal/clerk-js/url.ts

📝 Walkthrough

Walkthrough

This PR fixes two unrelated bugs: redirect URLs with non-default ports are now matched against wildcard allowedRedirectOrigins entries, and Client.fromJSON now reuses an existing SignUp instance without an id during SSO-to-signup transitions.

Changes

Redirect Origin Port Matching

Layer / File(s) Summary
Portless origin matching in isAllowedRedirect
packages/shared/src/internal/clerk-js/url.ts, packages/shared/src/internal/clerk-js/__tests__/url.test.ts, .changeset/fix-allowed-redirect-origins-port.md
Computes a portless origin for non-default ports, checks wildcard allowlist entries against both the trimmed origin and the portless origin, and adds test coverage plus release notes.

Stale SignUp Reference Fix

Layer / File(s) Summary
In-place SignUp update on missing id
packages/clerk-js/src/core/resources/Client.ts, .changeset/fix-signup-future-sso-transfer-stale-ref.md
Client.fromJSON updates the current SignUp instance when it has no id yet instead of replacing it, preserving the reference used by signUp.update(), and documents the patch release.

Estimated code review effort: 2 (Simple) | ~12 minutes

Possibly related issues

Suggested reviewers: wobsoriano

Poem

A rabbit hopped through ports at night,
Chasing origins, trimmed just right 🐇
A stale sign-up id, now held tight,
No more 405s to give a fright,
Hop, patch, release — all feels light! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The redirect-origin port allowlist fix and its tests/changeset are unrelated to #8338's sign-up flow bug. Split the redirect-origin port fix into a separate PR or remove it from this change set so the PR stays focused on #8338.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main fix: preserving the SignUpFuture reference during the SSO-to-sign-up transition.
Linked Issues check ✅ Passed The PR fixes #8338 by reusing the existing SignUp instance so signUp.update() keeps the active sign-up id and hits the correct endpoint.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/clerk-js/src/core/resources/Client.ts (2)

146-154: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix logic correctly addresses the stale SignUp reference bug.

The broadened condition if (data.sign_up && this.signUp instanceof SignUp && (this.signUp.id === data.sign_up.id || !this.signUp.id)) correctly preserves the existing SignUp instance (and thus the SignUpFuture reference) when the current instance has no id yet, matching the fix described in the PR objectives. The downstream path()/isNew() mechanics in Base.ts mean this instance will now correctly route PATCH requests to /client/sign_ups/{id} once the id arrives.

One minor readability nit: BaseResource already exposes a public isNew() helper (return !this.id) that is used elsewhere for the same semantic check (e.g. path()). Using this.signUp.isNew() instead of !this.signUp.id here would be more idiomatic and self-documenting.

♻️ Optional refactor for consistency
-      if (data.sign_up && this.signUp instanceof SignUp && (this.signUp.id === data.sign_up.id || !this.signUp.id)) {
+      if (data.sign_up && this.signUp instanceof SignUp && (this.signUp.id === data.sign_up.id || this.signUp.isNew())) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/clerk-js/src/core/resources/Client.ts` around lines 146 - 154, The
SignUp update branch in Client should use the existing resource helper for the
“no id yet” check instead of reading the id directly. In the conditional around
this.signUp.__internal_updateFromJSON, replace the raw !this.signUp.id part with
this.signUp.isNew() so the logic matches BaseResource semantics and stays
consistent with path() and other resource checks.

141-171: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for the no-id → id sign-up transition. Existing coverage checks matching ids, but not the case where this.signUp.id is unset and fromJSON() receives a sign_up payload. A unit test asserting the same SignUp instance is reused and its id is populated would lock in the SSO→sign-up flow behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/clerk-js/src/core/resources/Client.ts` around lines 141 - 171, Add a
regression test for Client.fromJSON covering the no-id to id sign-up transition:
exercise the branch where this.signUp is an existing SignUp with no id and
data.sign_up has an id, and verify the same SignUp instance is reused rather
than replaced. Assert that __internal_updateFromJSON is effectively applied by
checking the original SignUp reference is preserved and its id is populated
after the call, alongside the existing matching-id coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/shared/src/internal/clerk-js/url.ts`:
- Around line 449-455: The allowlist check in the URL validation logic is too
permissive because `portlessOrigin` is being matched for every pattern, causing
exact entries in the `isAllowed` flow to accept any port on the same host.
Update the logic in `url.ts` so only explicit wildcard or dev-only allowlist
entries can use the portless fallback, while exact allowlist strings remain
port-sensitive. Keep the fix localized to the `isAllowed` computation and the
`patterns.some(...)` matching path so `https://www.clerk.com` does not
implicitly allow `https://www.clerk.com:3000`.

---

Nitpick comments:
In `@packages/clerk-js/src/core/resources/Client.ts`:
- Around line 146-154: The SignUp update branch in Client should use the
existing resource helper for the “no id yet” check instead of reading the id
directly. In the conditional around this.signUp.__internal_updateFromJSON,
replace the raw !this.signUp.id part with this.signUp.isNew() so the logic
matches BaseResource semantics and stays consistent with path() and other
resource checks.
- Around line 141-171: Add a regression test for Client.fromJSON covering the
no-id to id sign-up transition: exercise the branch where this.signUp is an
existing SignUp with no id and data.sign_up has an id, and verify the same
SignUp instance is reused rather than replaced. Assert that
__internal_updateFromJSON is effectively applied by checking the original SignUp
reference is preserved and its id is populated after the call, alongside the
existing matching-id coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e0af06f2-d013-4b37-b8e8-8e4a7c8c9fc7

📥 Commits

Reviewing files that changed from the base of the PR and between 668b1c8 and 9a32372.

📒 Files selected for processing (5)
  • .changeset/fix-allowed-redirect-origins-port.md
  • .changeset/fix-signup-future-sso-transfer-stale-ref.md
  • packages/clerk-js/src/core/resources/Client.ts
  • packages/shared/src/internal/clerk-js/__tests__/url.test.ts
  • packages/shared/src/internal/clerk-js/url.ts

Comment thread packages/shared/src/internal/clerk-js/url.ts Outdated
Exact string entries in allowedRedirectOrigins (e.g. https://www.clerk.com)
were also matching ports they never declared because the portless-origin
test ran against all patterns. Port is part of the browser origin, so an
exact entry must remain port-sensitive.

The portless fallback now only applies when the original entry is a glob
(contains '*'), which covers the satellite-app use case where the pattern
is https://*.example.net and the dev server runs on :5173.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant