Skip to content

feat(clerk-js,ui): Add OAuth transport support for external auth flows#8831

Merged
wobsoriano merged 17 commits into
mainfrom
rob/USER-5494-poc
Jun 12, 2026
Merged

feat(clerk-js,ui): Add OAuth transport support for external auth flows#8831
wobsoriano merged 17 commits into
mainfrom
rob/USER-5494-poc

Conversation

@wobsoriano

@wobsoriano wobsoriano commented Jun 11, 2026

Copy link
Copy Markdown
Member

Description

Adds an internal OAuth transport seam that lets Clerk UI auth flows hand off OAuth to an external runtime, such as Electron opening the system browser, while reusing the existing Clerk redirect/popup initiation logic.

This is intended to support @clerk/electron without making the core UI components directly know about Electron.

Electron cannot safely use the normal embedded browser redirect behavior for OAuth/SSO. The Electron SDK needs a way to open OAuth in the system browser and return to the app through a deep link, without duplicating Clerk’s OAuth initiation logic in UI code.

This keeps the runtime-specific behavior behind one internal transport primitive instead of adding Electron-specific branching throughout the components.

Note

Native transport error propagation is not fully wired yet. We will pass the callback error through the deep-link query params in a follow-up, so this PR only establishes the transport success path and callback resumption.

What changed

  • Adds __internal_oauthTransport as an internal Clerk option.
  • Reuses the existing authenticateWithRedirectOrPopup flow and captures the generated OAuth verification URL instead of navigating in-browser.
  • Adds transport support for:
    • SignIn social OAuth buttons
    • SignUp social OAuth buttons
    • UserProfile connected account linking
  • Generalizes the resource callback handling so the external transport can resume through the existing redirect callback state machine.
  • Keeps enterprise SSO and enterprise account linking out of scope for this first pass.

Usage

The Electron SDK can register an internal OAuth transport on ClerkProvider. The UI components still use the normal Clerk APIs, but OAuth handoff happens through Electron IPC instead of browser navigation.

import { ClerkProvider } from '@clerk/react';

export function App() {
  return (
    <ClerkProvider
      publishableKey={PUBLISHABLE_KEY}
      __internal_oauthTransport={{
        getRedirectUrl: () => window.__clerk_internal_electron.getOAuthRedirectUrl(),
        open: url => window.__clerk_internal_electron.openOAuthUrl(url.href),
      }}
    >
      <SignIn />
    </ClerkProvider>
  );
}

The Electron preload/main process owns window.__clerk_internal_electron: it opens the OAuth URL in the system browser, waits for the deep-link callback, then resolves with the callback URL so Clerk can resume the existing redirect callback flow.

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

    • External OAuth transport for native/desktop apps to run OAuth/SSO via the system browser.
    • Optional navigation hook to customize post-auth navigation when completing OAuth callbacks.
    • UI now supplies internal callback parameters and adjusts popup vs. redirect behavior for SSO flows.
  • Bug Fixes / Improvements

    • More robust OAuth callback handling; improved error and idle-state behavior in social buttons and account-connect flows.
  • Tests

    • Expanded test coverage for transport flows, redirects, and related UI behaviors.

@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 12, 2026 10:11pm
swingset Ready Ready Preview, Comment Jun 12, 2026 10:11pm

Request Review

@changeset-bot

changeset-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 229b47a

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

This PR includes changesets to release 21 packages
Name Type
@clerk/shared Minor
@clerk/clerk-js Minor
@clerk/react Minor
@clerk/ui Minor
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/expo-passkeys Patch
@clerk/expo 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/tanstack-react-start Patch
@clerk/testing 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

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an internal OAuthTransport type and Clerk hooks, routes SignIn/SignUp redirect flows through a transport-aware helper, updates UI components to supply internal callback params and transport-aware behavior, and adds tests and a changeset marking minor bumps.

Changes

OAuth transport implementation across shared, clerk-js, and UI

Layer / File(s) Summary
Shared transport and callback contracts
packages/shared/src/types/oauthTransport.ts, packages/shared/src/types/index.ts, packages/shared/src/types/clerk.ts, packages/shared/src/types/redirects.ts
OAuthTransport type contract added. Clerk types extended with __internal_oauthTransport, __internal_hasOAuthTransport, __internal_handleResourceCallback. HandleOAuthCallbackParams gains optional navigateOnSetActive. AuthenticateWithRedirectParams gains __internal_callbackParams.
Clerk transport state and resource callback entrypoints
packages/clerk-js/src/core/clerk.ts, packages/clerk-js/src/core/__tests__/clerk.test.ts, packages/react/src/isomorphicClerk.ts
Clerk stores transport from options (#oauthTransport), exposes getters, adds __internal_handleResourceCallback, and delegates Google One Tap to it. IsomorphicClerk mirrors access. Tests verify initialization and delegation.
navigateOnSetActive routing in redirect callback completion
packages/clerk-js/src/core/clerk.ts, packages/clerk-js/src/core/__tests__/clerk.test.ts
Redirect completion branches now optionally call params.navigateOnSetActive({ session, redirectUrl, decorateUrl }) before falling back to existing navigation flows. Tests added for sign-in/sign-up completion cases.
Transport orchestration utility and resource routing
packages/clerk-js/src/utils/authenticateWithTransport.ts, packages/clerk-js/src/utils/__tests__/authenticateWithTransport.test.ts, 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
Adds _authenticateWithTransport to orchestrate external verification URL retrieval, transport.open, optional nonce reload, and final __internal_handleResourceCallback. SignIn/SignUp authenticateWithRedirect delegate to it when transport exists. Tests cover nonce handling, missing verification URL, rejections, and callback dispatch.
UI OAuth callback parameter builders and route prop wiring
packages/ui/src/components/SignIn/buildOAuthCallbackParams.ts, packages/ui/src/components/SignIn/__tests__/buildOAuthCallbackParams.test.ts, packages/ui/src/components/SignIn/index.tsx
Adds buildSignInOAuthCallbackParams and buildSignUpOAuthCallbackParams and refactors SSO callback routes to spread builder output. Tests validate exact shapes and omission of navigateOnSetActive for non-transport builders.
Transport-aware social button redirect behavior
packages/ui/src/components/SignIn/SignInSocialButtons.tsx, packages/ui/src/components/SignIn/__tests__/SignInSocialButtons.test.tsx, packages/ui/src/components/SignUp/SignUpSocialButtons.tsx, packages/ui/src/components/SignUp/__tests__/SignUpSocialButtons.test.tsx, packages/ui/bundlewatch.config.json
Social buttons now pass __internal_callbackParams (including navigateOnSetActive) to redirect flows, adjust popup/idle logic when a transport exists, call card.setIdle() on transport-path errors, and update tests and bundle budget support.
UserProfile connected account transport flow
packages/ui/src/components/UserProfile/ConnectedAccountsMenu.tsx, packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx, packages/ui/src/components/UserProfile/oauthTransport.ts, packages/ui/src/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx
Connected account create/reconnect flows accept explicit redirect URLs, use transport.getRedirectUrl()/open when available, decorate modal state, reload user after transport completion, and update tests to assert redirect modal state and transport usage.
Changeset release note and bumps
.changeset/oauth-transport.md
Marks @clerk/shared, @clerk/clerk-js, and @clerk/ui as minor; documents the internal OAuth transport for native wrappers and clarifies no change when no transport is registered.

Sequence Diagram(s)

sequenceDiagram
  participant Resource as SignIn/SignUp Resource
  participant AuthUtil as _authenticateWithTransport
  participant OAuthTransport
  participant Clerk

  Resource->>AuthUtil: authenticateWithRedirect(params)
  AuthUtil->>OAuthTransport: getRedirectUrl()
  AuthUtil->>Resource: authenticateMethod(..., { redirectUrl })
  Resource-->>AuthUtil: verificationUrl
  AuthUtil->>OAuthTransport: open(verificationUrl)
  OAuthTransport-->>AuthUtil: { callbackUrl }
  AuthUtil->>Resource: reload({ rotatingTokenNonce? })
  AuthUtil->>Clerk: __internal_handleResourceCallback(resource, callbackParams)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

"🐰 I fetched the redirect, opened the gate,
A nonce, a callback — what a fate!
Through native brows' bright chrome-lit arc,
I hop the user safely back to the park. 🥕"

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding OAuth transport support for external auth flows (non-browser-based OAuth handling). It is specific, clear, and represents the primary feature introduced across clerk-js and ui packages.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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


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

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-12T22:13:21.019Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 3
🔴 Breaking changes 0
🟡 Non-breaking changes 4
🟢 Additions 6

🤖 This report was reviewed by claude-sonnet-4-6.

Note
Break Check could not snapshot 1 subpath; the diff below excludes them.

  • @clerk/astro ./env: ambient declaration file (no top-level import or export): API Extractor can only analyze module entry points, so this global-augmentation surface cannot be snapshotted; add the subpath to ignoreSubpaths to acknowledge it (API Extractor: Unable to determine module for: /home/runner/_work/javascript/javascript/packages/astro/env.d.ts)

@clerk/clerk-js

Current version: 6.16.1
Recommended bump: MINOR → 6.17.0

Subpath .

🟢 Additions (3)

Added: Clerk.__internal_handleResourceCallback
+ __internal_handleResourceCallback: (signInOrUp: SignInResource | SignUpResource, params: HandleOAuthCallbackParams, customNavigate?: (to: string) => Promise<unknown>) => Promise<unknown>;

Added property Clerk.__internal_handleResourceCallback

Added: Clerk.__internal_hasOAuthTransport
+ get __internal_hasOAuthTransport(): boolean;

Added property Clerk.__internal_hasOAuthTransport

Added: Clerk.__internal_oauthTransport
+ get __internal_oauthTransport(): OAuthTransport | null;

Added property Clerk.__internal_oauthTransport

Subpath ./no-rhc

🟢 Additions (3)

Added: Clerk.__internal_handleResourceCallback
+ __internal_handleResourceCallback: (signInOrUp: SignInResource | SignUpResource, params: HandleOAuthCallbackParams, customNavigate?: (to: string) => Promise<unknown>) => Promise<unknown>;

Added property Clerk.__internal_handleResourceCallback

Added: Clerk.__internal_hasOAuthTransport
+ get __internal_hasOAuthTransport(): boolean;

Added property Clerk.__internal_hasOAuthTransport

Added: Clerk.__internal_oauthTransport
+ get __internal_oauthTransport(): OAuthTransport | null;

Added property Clerk.__internal_oauthTransport


@clerk/shared

Current version: 4.17.1
Recommended bump: MINOR → 4.18.0

Subpath ./types

🟡 Non-breaking Changes (3)

Modified: AuthenticateWithRedirectParams
// ... 7 unchanged lines elided ...
    emailAddress?: string;
    legalAccepted?: boolean;
    oidcPrompt?: string;
+   __internal_callbackParams?: HandleOAuthCallbackParams;
    enterpriseConnectionId?: string;
  };

Static analyzer: Breaking change in type alias AuthenticateWithRedirectParams: Type changed: {redirectUrl:string;redirectUrlComplete:string;continueSignUp?:boolean;continueSignIn?:boolean;strategy:import("@clerk/…{redirectUrl:string;redirectUrlComplete:string;continueSignUp?:boolean;continueSignIn?:boolean;strategy:import("@clerk/…

🤖 AI review (reclassified as non-breaking) (95%): A new optional property __internal_callbackParams was added to AuthenticateWithRedirectParams, which is used only as a function parameter (input type); adding an optional field to an input type is non-breaking for existing callers who don't pass it.

Modified: ClerkOptions
// ... 30 unchanged lines elided ...
    __internal_keyless_claimKeylessApplicationUrl?: string;
    __internal_keyless_copyInstanceKeysUrl?: string;
    __internal_keyless_dismissPrompt?: (() => Promise<void>) | null;
+   __internal_oauthTransport?: OAuthTransport;
    taskUrls?: Partial<Record<SessionTask['key'], string>>;
  };

Static analyzer: Breaking change in type alias ClerkOptions: Type changed: import("@clerk/shared").AfterMultiSessionSingleSignOutUrl&import("@clerk/shared").AfterSignOutUrl&import("@clerk/shared…import("@clerk/shared").AfterMultiSessionSingleSignOutUrl&import("@clerk/shared").AfterSignOutUrl&import("@clerk/shared…

🤖 AI review (reclassified as non-breaking) (95%): A new optional property __internal_oauthTransport was added to ClerkOptions, which is used as a function parameter/input type; adding an optional field to an input type does not break existing callers who don't supply it.

Modified: HandleOAuthCallbackParams
// ... 8 unchanged lines elided ...
    verifyPhoneNumberUrl?: string | null;
    reloadResource?: 'signIn' | 'signUp';
    unsafeMetadata?: SignUpUnsafeMetadata;
+   __internal_navigateOnSetActive?: (opts: {
+     session: SessionResource;
+     redirectUrl: string;
+     decorateUrl: (url: string) => string;
+   }) => Promise<unknown>;
  };

Static analyzer: Breaking change in type alias HandleOAuthCallbackParams: Type changed: import("@clerk/shared").SignInFallbackRedirectUrl&import("@clerk/shared").SignInForceRedirectUrl&import("@clerk/shared"…import("@clerk/shared").SignInFallbackRedirectUrl&import("@clerk/shared").SignInForceRedirectUrl&import("@clerk/shared"…

🤖 AI review (reclassified as non-breaking) (95%): A new optional property __internal_navigateOnSetActive was added to HandleOAuthCallbackParams, which is used only as a function parameter (input type); adding an optional field to an input type is non-breaking for existing callers who don't pass it.


@clerk/vue

Current version: 2.4.2
Recommended bump: MINOR → 2.5.0

🟡 Non-breaking Changes (1)

Modified: AuthenticateWithRedirectCallback

// ... 8 unchanged lines elided ...
      verifyPhoneNumberUrl?: string | null;
      reloadResource?: "signIn" | "signUp";
      unsafeMetadata?: SignUpUnsafeMetadata;
+     __internal_navigateOnSetActive?: (opts: {
+         session: import("@clerk/shared/types").SessionResource;
+         redirectUrl: string;
+         decorateUrl: (url: string) => string;
+     }) => Promise<unknown>;
  } & {}, import("vue").PublicProps>

Static analyzer: Breaking change in variable AuthenticateWithRedirectCallback: Type changed: import("@vue/runtime-core").DefineSetupFnComponent<import("@clerk/shared").HandleOAuthCallbackParams,{},{},import("@cle…import("@vue/runtime-core").DefineSetupFnComponent<import("@clerk/shared").HandleOAuthCallbackParams,{},{},import("@cle…

🤖 AI review (reclassified as non-breaking) (90%): The change adds a new optional property __internal_navigateOnSetActive to the props type of the component; existing consumers who don't pass this prop are unaffected, and the __internal_ prefix signals it is an internal detail not intended for direct use by downstream callers.


Report generated by Break Check

Last ran on 229b47a.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx (1)

132-155: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't fall through to navigate('') when reconnect returns no redirect URL.

response.verification?.externalVerificationRedirectURL is optional here, but the web fallback coerces the missing case into navigate(''), and the transport branch just returns. That turns an incomplete reconnect response into a broken/no-op flow instead of an actionable error. Please guard the URL first and send the missing-data case through handleError.

🤖 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/ui/src/components/UserProfile/ConnectedAccountsSection.tsx` around
lines 132 - 155, The web fallback currently calls navigate('') when
response.verification?.externalVerificationRedirectURL is missing; update the
logic in ConnectedAccountsSection.tsx after the createExternalAccount /
account.reauthorize call to first check that
response?.verification?.externalVerificationRedirectURL (or its href) exists and
is non-empty, and only call navigate(...) when it does; if it is missing, call
handleError(...) with an appropriate error/context (same error path you expect
for incomplete reconnects) and do not fall through to navigate; mirror the
transport branch behavior (which returns early) by returning after handling the
error or successful open so you don't continue into navigate on missing data.
🧹 Nitpick comments (2)
packages/ui/src/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx (1)

140-177: ⚡ Quick win

Add a transport reconnect test for the new ConnectedAccount.reconnect path.

The added transport coverage only exercises the add-connection menu. The reconnect flow now has separate transport behavior (getRedirectUrl, reauthorize vs createExternalAccount, open, user.reload), but none of that is asserted in this suite yet.

As per coding guidelines, **/*.{test,spec}.{ts,tsx}: Unit tests are required for all new functionality.

🤖 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/ui/src/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx`
around lines 140 - 177, Add a unit test in ConnectedAccountsSection.test.tsx
that covers the new reconnect path on ConnectedAccount.reconnect: set up a
fixture with an existing external account, register a mock
__internal_oauthTransport that implements getRedirectUrl and reauthorize
(mockResolvedValue), render the component and trigger theReconnect UI action
(click the reconnect button), then assert that
transport.getRedirectUrl/reauthorize were called with the expected URL/params,
that user.reload was called, and that the add-connection flow helpers
(createExternalAccount/open) were not invoked for this reconnect path; reference
the test helpers/objects fixtures.clerk,
fixtures.clerk.__internal_oauthTransport, ConnectedAccount.reconnect, and
fixtures.clerk.user!.reload when implementing the assertions.

Source: Coding guidelines

packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts (1)

38-79: ⚡ Quick win

Consider additional test coverage for transport behavior.

The test validates the happy path but could be more comprehensive:

  1. Missing assertion: The test name claims the transport routes "instead of windowNavigate," but there's no assertion verifying that windowNavigate (or similar browser redirect mechanisms) is NOT called. Consider adding a spy to verify the normal redirect path is bypassed.

  2. Error handling: No coverage for what happens when transport.open rejects or when handleResourceCallback fails. These paths should be tested to ensure proper error propagation.

  3. Backwards compatibility: Consider adding a test case where no transport is registered to verify the existing redirect behavior still works (regression protection).

📋 Suggested additional test cases
it('falls back to normal redirect when no transport is registered', async () => {
  const mockNavigate = vi.fn();
  SignUp.clerk = {
    buildUrlWithAuth: vi.fn(u => u),
    // No __internal_oauthTransport
    __internal_environment: { displayConfig: { captchaOauthBypass: [] } },
  } as any;
  
  // Mock windowNavigate or similar
  const navigateSpy = vi.spyOn(window, 'location', 'set');
  
  const mockFetch = vi.fn().mockResolvedValueOnce({
    client: null,
    response: {
      id: 'signup_123',
      verifications: {
        external_account: {
          status: 'unverified',
          external_verification_redirect_url: 'https://provider.example/auth',
        },
      },
    },
  });
  BaseResource._fetch = mockFetch;
  
  const signUp = new SignUp();
  await signUp.authenticateWithRedirect({
    strategy: 'oauth_google',
    redirectUrl: '/sso-callback',
    redirectUrlComplete: '/',
  } as any);
  
  // Verify normal redirect occurred
  expect(navigateSpy).toHaveBeenCalled();
});

it('handles transport rejection gracefully', async () => {
  const open = vi.fn().mockRejectedValue(new Error('Transport unavailable'));
  SignUp.clerk = {
    buildUrlWithAuth: vi.fn(u => u),
    __internal_oauthTransport: { getRedirectUrl: () => 'myapp://sso-callback', open },
    __internal_handleResourceCallback: vi.fn(),
    __internal_environment: { displayConfig: { captchaOauthBypass: [] } },
  } as any;
  
  const mockFetch = vi.fn().mockResolvedValueOnce({
    client: null,
    response: {
      id: 'signup_123',
      verifications: {
        external_account: {
          status: 'unverified',
          external_verification_redirect_url: 'https://provider.example/auth',
        },
      },
    },
  });
  BaseResource._fetch = mockFetch;
  
  const signUp = new SignUp();
  
  await expect(
    signUp.authenticateWithRedirect({
      strategy: 'oauth_google',
      redirectUrl: '/sso-callback',
      redirectUrlComplete: '/',
      __internal_callbackParams: { signInUrl: '/sign-in' },
    } as any)
  ).rejects.toThrow('Transport unavailable');
});
🤖 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/__tests__/SignUp.test.ts` around lines
38 - 79, Test currently only asserts the happy path through the OAuth transport
(SignUp.authenticateWithRedirect using
SignUp.clerk.__internal_oauthTransport.open) but misses verifying the normal
browser redirect is not invoked and lacks failure/back-compat cases; add three
tests: (1) spy on the browser navigation mechanism (e.g., spyOn(window,
'location', 'set') or the project's windowNavigate helper) and assert it is NOT
called when __internal_oauthTransport is present, (2) a test where
__internal_oauthTransport.open rejects and assert authenticateWithRedirect
rejects with that error (and that __internal_handleResourceCallback is not
called), and (3) a fallback test with no __internal_oauthTransport registered
that asserts the code falls back to the normal redirect path (using
BaseResource._fetch to return the external_verification_redirect_url and
verifying the window navigation spy was called).
🤖 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/ui/src/components/UserProfile/ConnectedAccountsMenu.tsx`:
- Around line 48-64: Both branches call createExternalAccount but only proceed
when res.verification?.externalVerificationRedirectURL exists; instead of
silently returning (transport branch) or doing nothing (web branch) when that
URL is missing, explicitly validate and surface the failure so the existing
catch/error path handles it: after calling createExternalAccount (in both the
clerk.__internal_oauthTransport branch and the window.location.href branch), if
the URL is falsy throw a descriptive Error('Missing
externalVerificationRedirectURL') (or invoke the shared error handler if one
exists) rather than returning or continuing, so the outer try/catch will route
the failure consistently; keep the existing successful flows that call
clerk.__internal_oauthTransport.open, user.reload, card.setIdle(strategy),
sleep, and navigate unchanged.

---

Outside diff comments:
In `@packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx`:
- Around line 132-155: The web fallback currently calls navigate('') when
response.verification?.externalVerificationRedirectURL is missing; update the
logic in ConnectedAccountsSection.tsx after the createExternalAccount /
account.reauthorize call to first check that
response?.verification?.externalVerificationRedirectURL (or its href) exists and
is non-empty, and only call navigate(...) when it does; if it is missing, call
handleError(...) with an appropriate error/context (same error path you expect
for incomplete reconnects) and do not fall through to navigate; mirror the
transport branch behavior (which returns early) by returning after handling the
error or successful open so you don't continue into navigate on missing data.

---

Nitpick comments:
In `@packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts`:
- Around line 38-79: Test currently only asserts the happy path through the
OAuth transport (SignUp.authenticateWithRedirect using
SignUp.clerk.__internal_oauthTransport.open) but misses verifying the normal
browser redirect is not invoked and lacks failure/back-compat cases; add three
tests: (1) spy on the browser navigation mechanism (e.g., spyOn(window,
'location', 'set') or the project's windowNavigate helper) and assert it is NOT
called when __internal_oauthTransport is present, (2) a test where
__internal_oauthTransport.open rejects and assert authenticateWithRedirect
rejects with that error (and that __internal_handleResourceCallback is not
called), and (3) a fallback test with no __internal_oauthTransport registered
that asserts the code falls back to the normal redirect path (using
BaseResource._fetch to return the external_verification_redirect_url and
verifying the window navigation spy was called).

In
`@packages/ui/src/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx`:
- Around line 140-177: Add a unit test in ConnectedAccountsSection.test.tsx that
covers the new reconnect path on ConnectedAccount.reconnect: set up a fixture
with an existing external account, register a mock __internal_oauthTransport
that implements getRedirectUrl and reauthorize (mockResolvedValue), render the
component and trigger theReconnect UI action (click the reconnect button), then
assert that transport.getRedirectUrl/reauthorize were called with the expected
URL/params, that user.reload was called, and that the add-connection flow
helpers (createExternalAccount/open) were not invoked for this reconnect path;
reference the test helpers/objects fixtures.clerk,
fixtures.clerk.__internal_oauthTransport, ConnectedAccount.reconnect, and
fixtures.clerk.user!.reload when implementing the assertions.
🪄 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

Run ID: 34b8dd7e-d1ba-47ad-933e-29e071fb8609

📥 Commits

Reviewing files that changed from the base of the PR and between d0ed42f and 78d0941.

📒 Files selected for processing (23)
  • .changeset/oauth-transport.md
  • packages/clerk-js/src/core/__tests__/clerk.test.ts
  • packages/clerk-js/src/core/clerk.ts
  • 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/__tests__/authenticateWithTransport.test.ts
  • packages/clerk-js/src/utils/authenticateWithTransport.ts
  • packages/shared/src/types/clerk.ts
  • packages/shared/src/types/index.ts
  • packages/shared/src/types/oauthTransport.ts
  • packages/shared/src/types/redirects.ts
  • packages/ui/src/components/SignIn/SignInSocialButtons.tsx
  • packages/ui/src/components/SignIn/__tests__/SignInSocialButtons.test.tsx
  • packages/ui/src/components/SignIn/__tests__/buildOAuthCallbackParams.test.ts
  • packages/ui/src/components/SignIn/buildOAuthCallbackParams.ts
  • packages/ui/src/components/SignIn/index.tsx
  • packages/ui/src/components/SignUp/SignUpSocialButtons.tsx
  • packages/ui/src/components/SignUp/__tests__/SignUpSocialButtons.test.tsx
  • packages/ui/src/components/UserProfile/ConnectedAccountsMenu.tsx
  • packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx
  • packages/ui/src/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx

Comment thread packages/ui/src/components/UserProfile/ConnectedAccountsMenu.tsx Outdated
@pkg-pr-new

pkg-pr-new Bot commented Jun 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 229b47a

Comment on lines +383 to +393
const transport = SignIn.clerk.__internal_oauthTransport;
if (transport) {
return _authenticateWithTransport({
clerk: SignIn.clerk,
transport,
resource: this,
authenticateMethod: this.authenticateWithRedirectOrPopup,
params,
callbackParams: params.__internal_callbackParams ?? {},
});
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If no transport is registered, the existing redirect behavior is preserved. If a transport is registered, we reuse the same private authenticateWithRedirectOrPopup initiation path through _authenticateWithTransport

* Exact callback params the SignIn `sso-callback` route passes to SSOCallback.
* Excludes `navigateOnSetActive`, which is transport-only.
*/
export function buildSignInOAuthCallbackParams(ctx: SignInContextType): HandleOAuthCallbackParams {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

mirror sthe props passed by the normal sso-callback routes. . In Electron, the renderer does not navigate to that route, so the social button passes the same callback context up front before the external transport opens

const redirectUrl = ctx.ssoCallbackUrl;
const redirectUrlComplete = ctx.afterSignInUrl || '/';
const shouldUsePopup = ctx.oauthFlow === 'popup' || (ctx.oauthFlow === 'auto' && originPrefersPopup());
const shouldUsePopup =

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

when an OAuth transport is registered, it takes precedence over oauthFlow. oauthFlow chooses between browser redirect and browser popup. Electron supplies a runtime transport instead, so popup/redirect should not be selected inside the renderer.

*
* @internal
*/
export async function _authenticateWithTransport(opts: {

@wobsoriano wobsoriano Jun 11, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is the core seam for Electron. It does not reimplement signIn.create, signUp.create, captcha retry, or other OAuth initiation logic. It calls the existing resource authenticateWithRedirectOrPopup method and only replaces the navigation callback so we can capture the provider verification URL and hand it to an external transport

The electron side of this is in #8835, where preload/main provide the transport implementation

const redirectUrl = isModal ? appendModalState({ url: window.location.href, componentName }) : window.location.href;

try {
const redirectUrl = clerk.__internal_oauthTransport

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

connected accounts do not go through authenticateWithRedirectOrPopup, so this component needs a small local transport branch. The branch only swaps the callback URL and external URL opening

@wobsoriano wobsoriano requested a review from a team June 11, 2026 18:16

@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.

🧹 Nitpick comments (4)
packages/react/src/isomorphicClerk.ts (2)

882-884: ⚡ Quick win

Add explicit return type annotation.

The getter lacks an explicit return type. As per coding guidelines, exported functions and public APIs should have explicit return types.

📝 Suggested fix
- get __internal_hasOAuthTransport() {
+ get __internal_hasOAuthTransport(): boolean {
    return this.clerkjs?.__internal_hasOAuthTransport || false;
  }
🤖 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/react/src/isomorphicClerk.ts` around lines 882 - 884, The getter
__internal_hasOAuthTransport currently infers its return type; explicitly
annotate it with a boolean return type by adding ": boolean" to the getter
signature in the isomorphicClerk class (update the getter named
__internal_hasOAuthTransport to declare its return type as boolean) so the
public API has an explicit return type.

Source: Coding guidelines


886-888: ⚡ Quick win

Add explicit return type annotation.

The getter lacks an explicit return type. As per coding guidelines, exported functions and public APIs should have explicit return types.

📝 Suggested fix
- get __internal_oauthTransport() {
+ get __internal_oauthTransport(): ReturnType<NonNullable<typeof this.clerkjs>['__internal_oauthTransport']> {
    return this.clerkjs?.__internal_oauthTransport || null;
  }

Alternatively, if the type is imported:

+ import type { OAuthTransport } from '`@clerk/shared/types`';
  
- get __internal_oauthTransport() {
+ get __internal_oauthTransport(): OAuthTransport | null {
    return this.clerkjs?.__internal_oauthTransport || null;
  }
🤖 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/react/src/isomorphicClerk.ts` around lines 886 - 888, The getter
__internal_oauthTransport in isomorphicClerk.ts lacks an explicit return type;
update its signature to include a precise type (e.g., OAuthTransport | null or
the correct type exported by clerk-js) and import that type if needed, so change
"get __internal_oauthTransport()" to "get __internal_oauthTransport():
OAuthTransport | null" (or the exact exported type) and ensure the
implementation still returns "this.clerkjs?.__internal_oauthTransport || null".

Source: Coding guidelines

packages/ui/src/components/UserProfile/oauthTransport.ts (2)

4-13: ⚡ Quick win

Consider adding JSDoc documentation for this exported function.

This exported function is part of the public API surface. As per coding guidelines, "All public APIs must be documented with JSDoc."

📝 Suggested JSDoc
+/**
+ * Extracts the external verification redirect URL from an OAuth external account resource.
+ * `@param` response - The external account resource from the OAuth flow
+ * `@returns` The verification redirect URL
+ * `@throws` {ClerkRuntimeError} When the verification URL is missing (code: oauth_missing_verification_url)
+ */
 export function getExternalVerificationRedirectURL(response: ExternalAccountResource | undefined): URL {
🤖 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/ui/src/components/UserProfile/oauthTransport.ts` around lines 4 -
13, Add JSDoc for the exported function getExternalVerificationRedirectURL:
document the function purpose, the parameter (response: ExternalAccountResource
| undefined), the return type (URL), and the thrown error (ClerkRuntimeError
when the verification URL is missing, include the error code
oauth_missing_verification_url). Keep the description concise, include `@param`,
`@returns`, and `@throws` tags, and mention that it extracts
verification.externalVerificationRedirectURL from the response.

Source: Coding guidelines


15-24: ⚡ Quick win

Consider adding JSDoc documentation for this exported function.

This exported function is part of the public API surface. As per coding guidelines, "All public APIs must be documented with JSDoc."

📝 Suggested JSDoc
+/**
+ * Reloads the user resource after an OAuth callback, handling rotating token nonces.
+ * `@param` user - The user resource to reload
+ * `@param` callbackUrl - The OAuth callback URL containing optional rotating_token_nonce query parameter
+ * `@returns` A promise that resolves when the user is reloaded
+ */
 export async function reloadUserAfterOAuthCallback(user: UserResource, callbackUrl: string): Promise<void> {
🤖 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/ui/src/components/UserProfile/oauthTransport.ts` around lines 15 -
24, The exported function reloadUserAfterOAuthCallback lacks JSDoc; add a JSDoc
block above reloadUserAfterOAuthCallback that documents the function purpose,
parameters (user: UserResource, callbackUrl: string), the behavior (extracts
rotating_token_nonce from callbackUrl and calls user.reload with
rotatingTokenNonce when present, otherwise calls user.reload without options),
and the Promise<void> return type; include any thrown errors or side effects and
tag it as part of the public API.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@packages/react/src/isomorphicClerk.ts`:
- Around line 882-884: The getter __internal_hasOAuthTransport currently infers
its return type; explicitly annotate it with a boolean return type by adding ":
boolean" to the getter signature in the isomorphicClerk class (update the getter
named __internal_hasOAuthTransport to declare its return type as boolean) so the
public API has an explicit return type.
- Around line 886-888: The getter __internal_oauthTransport in
isomorphicClerk.ts lacks an explicit return type; update its signature to
include a precise type (e.g., OAuthTransport | null or the correct type exported
by clerk-js) and import that type if needed, so change "get
__internal_oauthTransport()" to "get __internal_oauthTransport(): OAuthTransport
| null" (or the exact exported type) and ensure the implementation still returns
"this.clerkjs?.__internal_oauthTransport || null".

In `@packages/ui/src/components/UserProfile/oauthTransport.ts`:
- Around line 4-13: Add JSDoc for the exported function
getExternalVerificationRedirectURL: document the function purpose, the parameter
(response: ExternalAccountResource | undefined), the return type (URL), and the
thrown error (ClerkRuntimeError when the verification URL is missing, include
the error code oauth_missing_verification_url). Keep the description concise,
include `@param`, `@returns`, and `@throws` tags, and mention that it extracts
verification.externalVerificationRedirectURL from the response.
- Around line 15-24: The exported function reloadUserAfterOAuthCallback lacks
JSDoc; add a JSDoc block above reloadUserAfterOAuthCallback that documents the
function purpose, parameters (user: UserResource, callbackUrl: string), the
behavior (extracts rotating_token_nonce from callbackUrl and calls user.reload
with rotatingTokenNonce when present, otherwise calls user.reload without
options), and the Promise<void> return type; include any thrown errors or side
effects and tag it as part of the public API.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro

Run ID: ed6ab9d1-52f3-466d-b11a-1cbcd69df598

📥 Commits

Reviewing files that changed from the base of the PR and between 09fcb3b and 469b4d0.

📒 Files selected for processing (5)
  • packages/react/src/isomorphicClerk.ts
  • packages/ui/src/components/UserProfile/ConnectedAccountsMenu.tsx
  • packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx
  • packages/ui/src/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx
  • packages/ui/src/components/UserProfile/oauthTransport.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx
  • packages/ui/src/components/UserProfile/ConnectedAccountsMenu.tsx

@jeremy-clerk jeremy-clerk 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.

👍

@wobsoriano wobsoriano merged commit 17e4164 into main Jun 12, 2026
47 checks passed
@wobsoriano wobsoriano deleted the rob/USER-5494-poc branch June 12, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants