Skip to content

feat(clerk-js): Sign up if missing#8030

Merged
dmoerner merged 5 commits intomainfrom
daniel/user-4596-v2
Mar 10, 2026
Merged

feat(clerk-js): Sign up if missing#8030
dmoerner merged 5 commits intomainfrom
daniel/user-4596-v2

Conversation

@dmoerner
Copy link
Contributor

@dmoerner dmoerner commented Mar 10, 2026

Description

This PR re-reverts #7749, which adds support for sign up if missing for custom flows. We do this by adding the optional signUpIfMissing param on creating a sign in, and adding support for captchas. This requires both supporting captchas on sign in if signUpIfMissing is true, and making sure we don't show a double captcha on sign up after transfer from a sign up if missing sign in.

The original PR had to be reverted because it did not properly render captchas on Enterprise SSO transfers to sign up. The root of this mistake was my confusion over a bug in the existing code: We bailed out early in shouldBypassCaptchaForAttempt if there was no strategy. This had the effect that we unconditionally showed captchas on all transfers. This was correct behavior for EnterpriseSSO, which does not bypass captcha on transfer. This was a bug for OAuth, since we never checked if we should bypass captchas for OAuth.

This PR is identical to the previous one, except the logic in shouldBypassCaptchaForAttempt is fixed: We do show captchas on EnterpriseSSO transfers, and we properly check the bypass list for OAuth transfers. I also added further unit tests to verify these fixes.

This was tested in staging with SCIM to confirm that we did not have the EnterpriseSSO Captcha regression.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features
    • Added sign_up_if_missing parameter to sign-in requests, enabling automatic account creation for users who don't yet have an account
    • Integrated CAPTCHA bot protection challenges into the sign-in flow when your security configuration requires it
    • Improved CAPTCHA bypass logic for passwordless authentication strategies, including email code, email link, and phone code verification

@changeset-bot
Copy link

changeset-bot bot commented Mar 10, 2026

🦋 Changeset detected

Latest commit: 36bf49c

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

This PR includes changesets to release 21 packages
Name Type
@clerk/clerk-js Minor
@clerk/shared Minor
@clerk/chrome-extension Patch
@clerk/expo Patch
@clerk/agent-toolkit Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/expo-passkeys Patch
@clerk/express Patch
@clerk/fastify 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

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
Copy link

vercel bot commented Mar 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Mar 10, 2026 5:19pm

Request Review

* We delegate bot detection to the following providers, instead of relying on turnstile exclusively
*/
protected shouldBypassCaptchaForAttempt(params: SignUpCreateParams) {
if (!params.strategy) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the previous logic, which unconditionally showed captchas on EnterpriseSSO and OAuth transfers, since transfers do not have a strategy. The former behavior is correct, the latter was a bug.

if (captchaOauthBypass.some(strategy => strategy === params.strategy)) {
return true;
// Check for transfer captcha bypass.
if (params.transfer) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The new logic explicitly checks transfer first and checks both paths that can lead to a bypass: OAuth strategy, and sign up if missing. We fall through on EnterpriseSSO transfers, which then ends up returning false, i.e., we do show captchas on Enterprise SSO.

if (captchaOauthBypass.some(strategy => strategy === params.strategy)) {
return true;
// Check for transfer captcha bypass.
if (params.transfer) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's exactly the same change for sign up future.

expect(CaptchaChallenge).not.toHaveBeenCalled();
});

it('does not skip captcha challenge for enterprise_sso transfer', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

New unit test to rule out regressions with enterprise_sso transfer.

expect(mockFetch.mock.calls[0]?.[0].body).toHaveProperty('captchaError', undefined);
});

it('does not skip captcha challenge for enterprise_sso transfer', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sign up future regression test for enterprise_sso transfer case.

@dmoerner dmoerner marked this pull request as ready for review March 10, 2026 17:37
@dmoerner dmoerner requested a review from wobsoriano March 10, 2026 17:37
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

This pull request adds support for the sign_up_if_missing parameter to SignIn.create with integrated CAPTCHA handling. Changes include: adding a new optional signUpIfMissing parameter to SignInCreateParams and SignInFutureCreateParams types; implementing CAPTCHA-related helper methods in both SignIn and SignInFuture classes to determine when CAPTCHA is required and obtain tokens; updating SignUp and SignUpFuture classes to refine CAPTCHA bypass logic for transfer scenarios using a new isSignUpIfMissingCaptchaBypassStrategy utility; extending the CaptchaOptions.action type to include 'signin'; and adding comprehensive test coverage for CAPTCHA integration across SignIn and SignUp flows.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat(clerk-js): Sign up if missing' accurately reflects the main change—introducing support for the signUpIfMissing parameter for custom sign-in flows, which is the primary feature across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/shared/src/types/signInCommon.ts`:
- Around line 165-170: The union branch that currently reads `| { transfer?:
boolean }` should require the discriminator by changing it to `| { transfer:
true }` so calls like `signIn.create({})` or `signIn.create({ transfer: false
})` no longer type-check; also update the intersected type that currently has `&
{ transfer?: boolean; signUpIfMissing?: boolean }` to remove the optional
`transfer?: boolean` (or make it `transfer?: false` where appropriate) so the
`transfer` discriminator is not reintroduced. Locate the relevant type in
packages/shared/src/types/signInCommon.ts (the union used by signIn.create) and
adjust the branches so the transfer-only variant is `{ transfer: true }` and the
other branches keep/allow `signUpIfMissing` as intended.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro

Run ID: 3b8bf2fc-b256-467a-b56b-b8b2e9f6a9ab

📥 Commits

Reviewing files that changed from the base of the PR and between 121da74 and 36bf49c.

📒 Files selected for processing (8)
  • .changeset/strict-needles-taste.md
  • packages/clerk-js/src/core/resources/SignIn.ts
  • packages/clerk-js/src/core/resources/SignUp.ts
  • packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts
  • packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts
  • packages/clerk-js/src/utils/captcha/types.ts
  • packages/shared/src/types/signInCommon.ts
  • packages/shared/src/types/signInFuture.ts

Comment on lines +165 to +170
| {
transfer?: boolean;
}
) & {
transfer?: boolean;
signUpIfMissing?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check the file structure and find the exact location
find . -name "signInCommon.ts" -type f

Repository: clerk/javascript

Length of output: 103


🏁 Script executed:

# Read the signInCommon.ts file to understand the type definition
wc -l packages/shared/src/types/signInCommon.ts

Repository: clerk/javascript

Length of output: 105


🏁 Script executed:

# Read the relevant section around lines 165-170
sed -n '150,185p' packages/shared/src/types/signInCommon.ts

Repository: clerk/javascript

Length of output: 787


🏁 Script executed:

# Read more context to understand the full type structure
sed -n '140,200p' packages/shared/src/types/signInCommon.ts

Repository: clerk/javascript

Length of output: 1294


🏁 Script executed:

# Check the SignIn.ts implementation mentioned in the review
sed -n '169,197p' packages/clerk-js/src/core/resources/SignIn.ts

Repository: clerk/javascript

Length of output: 1027


🏁 Script executed:

# Get more context around that area
sed -n '160,210p' packages/clerk-js/src/core/resources/SignIn.ts

Repository: clerk/javascript

Length of output: 1622


Require transfer: true for the transfer-only variant.

The new | { transfer?: boolean } branch allows signIn.create({}) and signIn.create({ transfer: false }) to type-check, but the implementation posts these payloads directly to the API without validation. This converts an invalid call into a runtime API failure instead of a compile-time rejection. Change the branch to | { transfer: true } to make transfer required and discriminating.

Suggested fix
-  | {
-      transfer?: boolean;
-    }
+  | {
+      transfer: true;
+    }
 ) & {
   transfer?: boolean;
   signUpIfMissing?: boolean;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| {
transfer?: boolean;
}
) & {
transfer?: boolean;
signUpIfMissing?: boolean;
| {
transfer: true;
}
) & {
transfer?: boolean;
signUpIfMissing?: boolean;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/types/signInCommon.ts` around lines 165 - 170, The union
branch that currently reads `| { transfer?: boolean }` should require the
discriminator by changing it to `| { transfer: true }` so calls like
`signIn.create({})` or `signIn.create({ transfer: false })` no longer
type-check; also update the intersected type that currently has `& { transfer?:
boolean; signUpIfMissing?: boolean }` to remove the optional `transfer?:
boolean` (or make it `transfer?: false` where appropriate) so the `transfer`
discriminator is not reintroduced. Locate the relevant type in
packages/shared/src/types/signInCommon.ts (the union used by signIn.create) and
adjust the branches so the transfer-only variant is `{ transfer: true }` and the
other branches keep/allow `signUpIfMissing` as intended.

Copy link
Member

@wobsoriano wobsoriano left a comment

Choose a reason for hiding this comment

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

thanks!

@dmoerner dmoerner enabled auto-merge (squash) March 10, 2026 17:58
@dmoerner
Copy link
Contributor Author

CI seems broken, I'm going to try converting to draft and then switching back to see if I can get it to re-run.

@dmoerner dmoerner disabled auto-merge March 10, 2026 20:21
@dmoerner dmoerner marked this pull request as draft March 10, 2026 20:21
@dmoerner dmoerner marked this pull request as ready for review March 10, 2026 20:21
@wobsoriano wobsoriano closed this Mar 10, 2026
@wobsoriano wobsoriano reopened this Mar 10, 2026
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 10, 2026

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@8030

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8030

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8030

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8030

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8030

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8030

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8030

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8030

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8030

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8030

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8030

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8030

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8030

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8030

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8030

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8030

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8030

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8030

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8030

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8030

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8030

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8030

commit: 36bf49c

@dmoerner dmoerner merged commit de1386f into main Mar 10, 2026
54 checks passed
@dmoerner dmoerner deleted the daniel/user-4596-v2 branch March 10, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants