Skip to content

chore(js,ui,shared): Correctly display OAuth consent redirect root domains#8700

Open
wobsoriano wants to merge 7 commits into
mainfrom
rob/user-5440-consent-page-doesnt-use-public-suffix-list-to-extract-root
Open

chore(js,ui,shared): Correctly display OAuth consent redirect root domains#8700
wobsoriano wants to merge 7 commits into
mainfrom
rob/user-5440-consent-page-doesnt-use-public-suffix-list-to-extract-root

Conversation

@wobsoriano
Copy link
Copy Markdown
Member

@wobsoriano wobsoriano commented May 28, 2026

Description

The OAuth consent page was computing the display domain client-side by splitting the hostname on dots, which breaks for ccTLDs like .co.uk and hosting platforms like github.io. This is a security concern since users make trust decisions based on that domain.

The backend already has full PSL support, so this PR threads redirect_uri through to GET /v1/me/oauth/consent/{clientID} and uses the resolved redirectDomain from the response instead. The client-side heuristic is kept as a fallback for IPs, localhost, and older FAPI versions.

Part of USER-5440

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:

…omains for known multi-label public suffixes
@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 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 May 30, 2026 2:48am

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 28, 2026

🦋 Changeset detected

Latest commit: 7bb2c0b

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

This PR includes changesets to release 2 packages
Name Type
@clerk/ui Patch
@clerk/chrome-extension 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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 28, 2026

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 7bb2c0b

@wobsoriano wobsoriano changed the title chore(clerk-js,ui,shared): Correctly display OAuth consent redirect domains for known multi-label public suffixes chore(js,ui,shared): Correctly display OAuth consent redirect root domains May 29, 2026
@wobsoriano wobsoriano marked this pull request as ready for review May 29, 2026 17:51
@wobsoriano wobsoriano requested a review from jfoshee May 29, 2026 17:51
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

API Changes Report

Generated by snapi on 2026-05-30T02:49:31.353Z

Summary

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

Warning
3 breaking change(s) detected - Major version bump required


@clerk/shared

Current version: 4.14.0
Recommended bump: MAJOR → 5.0.0

Subpath ./react

🔴 Breaking Changes (1)

Changed: UseOAuthConsentParams
- type UseOAuthConsentParams = Pick<GetOAuthConsentInfoParams, 'oauthClientId' | 'scope'> & {
+ type UseOAuthConsentParams = Pick<GetOAuthConsentInfoParams, 'oauthClientId' | 'scope' | 'redirectUri'> & {
    keepPreviousData?: boolean;
    enabled?: boolean;
  };

Breaking change in type alias UseOAuthConsentParams: Type changed: Pick<GetOAuthConsentInfoParams,'oauthClientId'|'scope'>&{keepPreviousData?:boolean;enabled?:boolean;}Pick<GetOAuthConsentInfoParams,'oauthClientId'|'scope'|'redirectUri'>&{keepPreviousData?:boolean;enabled?:boolean;}

Subpath ./types

🔴 Breaking Changes (2)

Changed: GetOAuthConsentInfoParams
  type GetOAuthConsentInfoParams = {
    oauthClientId: string;
    scope?: string;
+   redirectUri?: string;
  };

Breaking change in type alias GetOAuthConsentInfoParams: Type changed: {oauthClientId:string;scope?:string;}{oauthClientId:string;scope?:string;redirectUri?:string;}

Changed: OAuthConsentInfo
// ... 3 unchanged lines elided ...
    oauthApplicationUrl: string;
    clientId: string;
    state: string;
+   redirectDomain: string | null;
    scopes: OAuthConsentScope[];
  };

Breaking change in type alias OAuthConsentInfo: Type changed: {oauthApplicationName:string;oauthApplicationLogoUrl:string;oauthApplicationUrl:string;clientId:string;state:string;sco…{oauthApplicationName:string;oauthApplicationLogoUrl:string;oauthApplicationUrl:string;clientId:string;state:string;red…


Report generated by snapi

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

Review Change Stack

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: 97f628d9-2bc1-4c16-9760-841b4aa1c22c

📥 Commits

Reviewing files that changed from the base of the PR and between f5732df and f54230a.

📒 Files selected for processing (1)
  • packages/ui/src/components/OAuthConsent/OAuthConsent.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui/src/components/OAuthConsent/OAuthConsent.tsx

📝 Walkthrough

Walkthrough

This PR adds support for displaying accurately resolved OAuth consent redirect domains using Public Suffix List resolution. The changes introduce a redirectUri parameter through the OAuth consent flow and return a redirectDomain field resolved by the backend. The implementation spans type definitions for the new data contract, OAuthApplication API updates to pass and receive the redirect information, React hook modifications to manage redirectUri as a query parameter and cache key input, OAuthConsent component updates to extract redirectUri from search parameters and display the resolved domain, and test updates validating the new parameter flow. A Changeset entry documents the fix for multi-label public suffix handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 reflects the main change: threading redirect_uri through to the backend and using the resolved redirectDomain for OAuth consent domain display instead of client-side splitting.
Description check ✅ Passed The description is directly related to the changeset, explaining the security concern with client-side domain computation, the backend PSL support being leveraged, and the fallback strategy for compatibility.
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.


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

🧹 Nitpick comments (1)
packages/ui/src/components/OAuthConsent/__tests__/OAuthConsent.test.tsx (1)

185-210: ⚡ Quick win

Add a regression test for redirectUrl override precedence.

Please add one public-path override test that sets redirectUrl via context props and asserts getConsentInfo receives that value as redirectUri. This protects the redirect-domain trust text path from source mismatch regressions.

As per coding guidelines: **/*.{test,spec}.{ts,tsx}: "Unit tests are required for all new functionality" and "Verify proper error handling and edge cases."

🤖 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/OAuthConsent/__tests__/OAuthConsent.test.tsx`
around lines 185 - 210, Add a regression test in the existing
OAuthConsent.test.tsx case that verifies public-path override precedence for
redirectUrl: reuse createFixtures/props.setProps (the same pattern used for
oauthClientId and scope overrides), set props.setProps({ componentName:
'OAuthConsent', redirectUrl: 'https://override.example/callback', oauthClientId:
'override_id', scope: 'openid email' }) and then mock getConsentInfo via
mockOAuthApplication(fixtures.clerk, { getConsentInfo }) and assert that
getConsentInfo was called with redirectUri: 'https://override.example/callback'
(i.e., expect(getConsentInfo).toHaveBeenCalledWith({ ..., redirectUri:
'https://override.example/callback' })), ensuring the test mirrors the existing
override test structure.
🤖 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/OAuthConsent/OAuthConsent.tsx`:
- Around line 53-57: The code uses getRedirectUriFromSearch() for the consent
fetch (passed into useOAuthConsent as redirectUri) but allows a different
redirectUrl to be shown in the UI, causing a mismatch; compute a single
resolvedRedirectUri once (e.g., const resolvedRedirectUri =
contextProvidedRedirectUrl ?? getRedirectUriFromSearch() ?? undefined) and use
that same resolvedRedirectUri when calling useOAuthConsent and when
rendering/displaying the redirect URL so both backend fetch and UI display use
the exact same value; update references to redirectUri/redirectUrl in
OAuthConsent (including the call site of useOAuthConsent and the display logic)
to use resolvedRedirectUri.

---

Nitpick comments:
In `@packages/ui/src/components/OAuthConsent/__tests__/OAuthConsent.test.tsx`:
- Around line 185-210: Add a regression test in the existing
OAuthConsent.test.tsx case that verifies public-path override precedence for
redirectUrl: reuse createFixtures/props.setProps (the same pattern used for
oauthClientId and scope overrides), set props.setProps({ componentName:
'OAuthConsent', redirectUrl: 'https://override.example/callback', oauthClientId:
'override_id', scope: 'openid email' }) and then mock getConsentInfo via
mockOAuthApplication(fixtures.clerk, { getConsentInfo }) and assert that
getConsentInfo was called with redirectUri: 'https://override.example/callback'
(i.e., expect(getConsentInfo).toHaveBeenCalledWith({ ..., redirectUri:
'https://override.example/callback' })), ensuring the test mirrors the existing
override test structure.
🪄 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: 61fb867e-80a7-4bfa-907a-c08e6c73b422

📥 Commits

Reviewing files that changed from the base of the PR and between b09788e and f5732df.

📒 Files selected for processing (8)
  • .changeset/quiet-terms-drum.md
  • packages/clerk-js/src/core/modules/oauthApplication/index.ts
  • packages/shared/src/react/hooks/useOAuthConsent.shared.ts
  • packages/shared/src/react/hooks/useOAuthConsent.tsx
  • packages/shared/src/react/hooks/useOAuthConsent.types.ts
  • packages/shared/src/types/oauthApplication.ts
  • packages/ui/src/components/OAuthConsent/OAuthConsent.tsx
  • packages/ui/src/components/OAuthConsent/__tests__/OAuthConsent.test.tsx

Comment on lines +53 to +57
const redirectUri = getRedirectUriFromSearch();
const { data, isLoading, error } = useOAuthConsent({
oauthClientId,
scope,
redirectUri: redirectUri || undefined,
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 29, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use one redirect URI source for both consent fetch and display.

Line 57 always sends URL-derived redirectUri, while Line 74 allows context to override displayed redirectUrl. If those differ, domainAction can reflect backend resolution for a different URI than the one intended by context.

Suggested fix
-  const redirectUri = getRedirectUriFromSearch();
+  const redirectUri = ctx.redirectUrl ?? getRedirectUriFromSearch();
   const { data, isLoading, error } = useOAuthConsent({
     oauthClientId,
     scope,
     redirectUri: redirectUri || undefined,
     // TODO: Remove this once account portal is refactored to use this component
     enabled: !hasContextCallbacks,
   });
@@
-  const redirectUrl = ctx.redirectUrl ?? redirectUri;
+  const redirectUrl = redirectUri;

Also applies to: 74-74, 90-90

🤖 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/OAuthConsent/OAuthConsent.tsx` around lines 53 -
57, The code uses getRedirectUriFromSearch() for the consent fetch (passed into
useOAuthConsent as redirectUri) but allows a different redirectUrl to be shown
in the UI, causing a mismatch; compute a single resolvedRedirectUri once (e.g.,
const resolvedRedirectUri = contextProvidedRedirectUrl ??
getRedirectUriFromSearch() ?? undefined) and use that same resolvedRedirectUri
when calling useOAuthConsent and when rendering/displaying the redirect URL so
both backend fetch and UI display use the exact same value; update references to
redirectUri/redirectUrl in OAuthConsent (including the call site of
useOAuthConsent and the display logic) to use resolvedRedirectUri.

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.

Is ctx just used for testing?

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find where useOAuthConsentContext is defined and how ctx is populated
rg -n "useOAuthConsentContext|OAuthConsentContext|OAuthConsentCtx" --type=ts --type=tsx -A 5

Length of output: 447


🏁 Script executed:

#!/bin/bash
rg -rn "useOAuthConsentContext\|OAuthConsentContext" -A 5

Length of output: 312


🏁 Script executed:

#!/bin/bash
rg -rn "useOAuthConsentContext" -A 5
rg -rn "OAuthConsentContext" -A 5

Length of output: 5573


🏁 Script executed:

#!/bin/bash
# Get the full provider value and OAuthConsentCtx type
cat packages/ui/src/contexts/components/OAuthConsent.ts
rg -n "oauthClientId\|onAllow\|onDeny\|redirectUrl\|OAuthConsentCtx" packages/ui/src/contexts/ClerkUIComponentsContext.tsx -A 3

Length of output: 937


🏁 Script executed:

#!/bin/bash
rg -rn "OAuthConsentCtx" -A 15
rg -n "oauthClientId\|onAllow\|onDeny\|redirectUrl" packages/ui/src/contexts/ClerkUIComponentsContext.tsx -B 2 -A 3

Length of output: 2748


🏁 Script executed:

#!/bin/bash
# Get the full OAuthConsentCtx type from types.ts
rg -n "componentName: 'OAuthConsent'" packages/ui/src/types.ts -A 50 | head -60

Length of output: 1934



@jfoshee No — ctx is a production integration point, not a test mechanism. According to the type definitions in packages/ui/src/types.ts, it represents the accounts portal path: a separate accounts portal service pre-populates all data (scopes, oauthApplicationName, redirectUrl, etc.) and provides onAllow/onDeny callbacks via context.

This also invalidates my earlier review comment. The mismatch I described can't occur in practice because:

  • ctx.redirectUrl is only populated on the accounts portal path, which always sets onAllow/onDeny.
  • The useOAuthConsent hook has enabled: !hasContextCallbacks, so it is disabled on the accounts portal path entirely.
  • Therefore, in the only path where the hook actually runs (the public path), ctx.redirectUrl is always undefined, and both redirectUri and redirectUrl consistently resolve to getRedirectUriFromSearch().

My original concern was a false positive — the current code is correct as-is. Sorry for the noise.


🧠 Learnings used
Learnt from: alexcarpenter
Repo: clerk/javascript PR: 8593
File: packages/ui/src/elements/ClipboardInput.tsx:0-0
Timestamp: 2026-05-19T17:58:25.594Z
Learning: When styling native HTML `<input type="text">` (or other single-line text inputs), applying `textOverflow: 'ellipsis'` by itself is sufficient—native single-line input behavior provides implicit single-line/overflow handling, so you generally do not need to also set `overflow: 'hidden'` or `whiteSpace: 'nowrap'`. Conversely, if you’re applying ellipsis to non-input/block-level elements (e.g., `<div>`, `<span>`, or other elements), ensure `whiteSpace: 'nowrap'` and `overflow: 'hidden'` are included alongside `textOverflow: 'ellipsis'` so the ellipsis can render.

Comment thread packages/ui/src/components/OAuthConsent/OAuthConsent.tsx Outdated
Co-authored-by: Jacob Foshee <jacobf@gmail.com>
Copy link
Copy Markdown
Contributor

@jfoshee jfoshee left a comment

Choose a reason for hiding this comment

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

Did you test w/ AP locally? You should be able to test it locally now using make up/local-js

Copy link
Copy Markdown
Member

@jacekradko jacekradko left a comment

Choose a reason for hiding this comment

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

Need to update the changeset

@@ -0,0 +1,5 @@
---
'@clerk/ui': patch
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks like this also touches shared and clerk-js

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.

3 participants