Skip to content

fix(shared): Correct redirect behavior for OAuth applications on /continue#8391

Open
gabrielmeloc22 wants to merge 3 commits intomainfrom
gabriel/fix-oauth-force-redirect-behavior
Open

fix(shared): Correct redirect behavior for OAuth applications on /continue#8391
gabrielmeloc22 wants to merge 3 commits intomainfrom
gabriel/fix-oauth-force-redirect-behavior

Conversation

@gabrielmeloc22
Copy link
Copy Markdown

@gabrielmeloc22 gabrielmeloc22 commented Apr 23, 2026

Description

When Clerk acts as an OAuth Identity Provider (e.g. for a desktop app using PKCE), the flow requires the user to sign in first, then return to /oauth/authorize/continue to complete the
authorization and issue a code to the requesting application.

After the user signs in (e.g. with Google), Clerk should redirect back to the FAPI continuation endpoint to finish the IDP flow. However, two issues prevent this:

  1. /oauth/authorize/continue and /oauth/authorize-with-immediate-redirect were not classified as requiring user input, causing #redirectFAPIInitiatedFlow to prematurely navigate away from the sign-in page — stripping the redirect_url from the URL before the component could read it.
  2. signInForceRedirectUrl overrides FAPI redirect URLs in the RedirectUrls priority chain. If a customer has forceRedirectUrl configured (directly or via env var), action_complete_redirect_url is set to the customer's home page instead of the FAPI continuation URL. The server then redirects there after the social sign-in callback, and the IDP flow never completes.

The result: the user is signed in successfully, but the desktop app never receives its authorization code. This only manifests in production — in development, #redirectFAPIInitiatedFlow masks the issue by auto-redirecting after sign-in.

Changes

url.ts:

  • Moved /oauth/authorize/continue and /oauth/authorize-with-immediate-redirect to frontendApiRedirectPathsWithUserInput so #redirectFAPIInitiatedFlow waits for the user to sign in before redirecting, preserving the redirect_url in the page URL.
  • Added isFAPIInitiatedFlowPath() helper that checks if a URL pathname matches any known FAPI flow endpoint (without requiring frontendApi host matching).

redirectUrls.ts:

  • In #getRedirectUrl, when redirect_url from search params points to a known FAPI endpoint, it now takes highest priority — overriding signInForceRedirectUrl. This ensures the IDP authorization flow always completes. For non-FAPI redirects, forceRedirectUrl still works as before.

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:

@gabrielmeloc22 gabrielmeloc22 requested review from a team and LauraBeatris April 23, 2026 14:00
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 23, 2026

Deployment failed with the following error:

You must set up Two-Factor Authentication before accessing this team.

View Documentation: https://vercel.com/docs/two-factor-authentication

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 23, 2026

🦋 Changeset detected

Latest commit: e3bdd9b

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

This PR includes changesets to release 20 packages
Name Type
@clerk/shared Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/clerk-js 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/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

@gabrielmeloc22 gabrielmeloc22 changed the title fix: correct force redirect behavior for oauth fix(clerk-js): correct force redirect behavior for oauth Apr 23, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro

Run ID: 1bbbd5cd-6732-412f-8de8-e1dde9585d56

📥 Commits

Reviewing files that changed from the base of the PR and between eec0d82 and e3bdd9b.

📒 Files selected for processing (1)
  • .changeset/red-windows-train.md
✅ Files skipped from review due to trivial changes (1)
  • .changeset/red-windows-train.md

📝 Walkthrough

Walkthrough

Adds detection and handling for FAPI-initiated OAuth redirect endpoints. url.ts updates the FAPI allowlist (moves /oauth/authorize-with-immediate-redirect to the "requires user input" set and adds /oauth/authorize/continue) and exports isFAPIInitiatedFlowPath(url: string) which parses absolute URLs, returns false for invalid/relative inputs, rejects same-origin URLs in the browser, and matches the pathname against the allowlist. redirectUrls.ts updates #getRedirectUrl to check fromSearchParams.redirectUrl and, if it is an FAPI-initiated flow path, return it immediately before existing force/fallback logic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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 summarizes the main change: fixing redirect behavior for OAuth applications on the /continue endpoint, which is the core issue this PR addresses.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, clearly explaining the OAuth flow issues, their causes, and the implemented solutions across both modified files.
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.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
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/internal/clerk-js/redirectUrls.ts`:
- Around line 102-109: The FAPI priority check uses isFAPIInitiatedFlowPath
which only inspects the pathname and can wrongly match non-FAPI hosts; change
the gating to verify the host as well by using the same logic as
isRedirectForFAPIInitiatedFlow (or call isRedirectForFAPIInitiatedFlow with the
redirect URL and this.frontendApi) instead of isFAPIInitiatedFlowPath in the
block that reads this.fromSearchParams.redirectUrl, and add unit tests verifying
(a) a URL on a non-FAPI host with an allowed FAPI path does NOT bypass
signInForceRedirectUrl, and (b) a valid FAPI continuation URL on the frontend
API host DOES bypass it.
🪄 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), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 0db0ca53-0f85-47d5-9227-342d40c911ad

📥 Commits

Reviewing files that changed from the base of the PR and between d9011b4 and 1459092.

📒 Files selected for processing (2)
  • packages/shared/src/internal/clerk-js/redirectUrls.ts
  • packages/shared/src/internal/clerk-js/url.ts

Comment on lines +102 to +109
// FAPI-initiated flow redirect URLs (e.g. /oauth/authorize/continue) must
// take highest priority to ensure the IDP authorization flow completes.
// Without this, a configured signInForceRedirectUrl would override the
// redirect_url needed to resume the OAuth IDP flow after sign-in.
const fapiRedirectUrl = this.fromSearchParams.redirectUrl;
if (fapiRedirectUrl && isFAPIInitiatedFlowPath(fapiRedirectUrl)) {
return fapiRedirectUrl;
}
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.

⚠️ Potential issue | 🟠 Major

FAPI priority check only validates pathname, not host — risk of matching non-FAPI URLs.

isFAPIInitiatedFlowPath (in url.ts, lines 427–435) checks only parsed.pathname against the allowlist, unlike the sibling isRedirectForFAPIInitiatedFlow which also verifies frontendApi === url.host. Any redirect_url whose path happens to be /oauth/authorize/continue, /oauth/authorize, /v1/verify, /oauth/end_session, etc. — even on the customer's own origin or another allowed origin — will now override signInForceRedirectUrl. This silently breaks the documented force-redirect contract for those paths and, depending on how the returned URL is later consumed, could also be abused as an open-redirect-ish escape from force URLs (the value still passes isAllowedRedirect, but force URL is a stronger configured invariant).

Suggest gating on the frontend API host as well, mirroring isRedirectForFAPIInitiatedFlow:

Proposed fix
-    // FAPI-initiated flow redirect URLs (e.g. /oauth/authorize/continue) must
-    // take highest priority to ensure the IDP authorization flow completes.
-    // Without this, a configured signInForceRedirectUrl would override the
-    // redirect_url needed to resume the OAuth IDP flow after sign-in.
-    const fapiRedirectUrl = this.fromSearchParams.redirectUrl;
-    if (fapiRedirectUrl && isFAPIInitiatedFlowPath(fapiRedirectUrl)) {
-      return fapiRedirectUrl;
-    }
+    // FAPI-initiated flow redirect URLs (e.g. /oauth/authorize/continue) must
+    // take highest priority to ensure the IDP authorization flow completes.
+    const fapiRedirectUrl = this.fromSearchParams.redirectUrl;
+    if (
+      fapiRedirectUrl &&
+      isRedirectForFAPIInitiatedFlow(this.options.frontendApi ?? '', fapiRedirectUrl)
+    ) {
+      return fapiRedirectUrl;
+    }

Also worth adding a unit test covering: (a) a non-FAPI-host URL whose path matches the allowlist should NOT bypass signInForceRedirectUrl, and (b) a genuine FAPI continuation URL should bypass it.

📝 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
// FAPI-initiated flow redirect URLs (e.g. /oauth/authorize/continue) must
// take highest priority to ensure the IDP authorization flow completes.
// Without this, a configured signInForceRedirectUrl would override the
// redirect_url needed to resume the OAuth IDP flow after sign-in.
const fapiRedirectUrl = this.fromSearchParams.redirectUrl;
if (fapiRedirectUrl && isFAPIInitiatedFlowPath(fapiRedirectUrl)) {
return fapiRedirectUrl;
}
// FAPI-initiated flow redirect URLs (e.g. /oauth/authorize/continue) must
// take highest priority to ensure the IDP authorization flow completes.
const fapiRedirectUrl = this.fromSearchParams.redirectUrl;
if (
fapiRedirectUrl &&
isRedirectForFAPIInitiatedFlow(this.options.frontendApi ?? '', fapiRedirectUrl)
) {
return fapiRedirectUrl;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/internal/clerk-js/redirectUrls.ts` around lines 102 -
109, The FAPI priority check uses isFAPIInitiatedFlowPath which only inspects
the pathname and can wrongly match non-FAPI hosts; change the gating to verify
the host as well by using the same logic as isRedirectForFAPIInitiatedFlow (or
call isRedirectForFAPIInitiatedFlow with the redirect URL and this.frontendApi)
instead of isFAPIInitiatedFlowPath in the block that reads
this.fromSearchParams.redirectUrl, and add unit tests verifying (a) a URL on a
non-FAPI host with an allowed FAPI path does NOT bypass signInForceRedirectUrl,
and (b) a valid FAPI continuation URL on the frontend API host DOES bypass it.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 23, 2026

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: e3bdd9b

@LauraBeatris LauraBeatris changed the title fix(clerk-js): correct force redirect behavior for oauth fix(clerk-js): Correct redirect behavior for OAuth applications on /continue Apr 23, 2026
@LauraBeatris LauraBeatris changed the title fix(clerk-js): Correct redirect behavior for OAuth applications on /continue fix(shared): Correct redirect behavior for OAuth applications on /continue Apr 23, 2026
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.

2 participants