Skip to content

fix(clerk-js): Prevent duplicate __client_uat cookies in iframe contexts#7875

Merged
brkalow merged 4 commits intomainfrom
bryce/fix-iframe-client-uat-cookie-domain
Feb 18, 2026
Merged

fix(clerk-js): Prevent duplicate __client_uat cookies in iframe contexts#7875
brkalow merged 4 commits intomainfrom
bryce/fix-iframe-client-uat-cookie-domain

Conversation

@brkalow
Copy link
Member

@brkalow brkalow commented Feb 18, 2026

Summary

  • When an app runs in both an iframe and a standalone tab, getCookieDomain() returns undefined in the iframe because the eTLD+1 test cookie probe fails (third-party cookie restrictions prevent domain cookies from sticking)
  • This causes clientUat.ts to set host-only cookies (no Domain attribute) in the iframe, while the non-iframe context sets domain-scoped cookies (with Domain attribute)
  • The browser treats these as two separate cookies, creating duplicate __client_uat entries with conflicting values
  • Fix: getCookieDomain() now falls back to hostname instead of undefined, so the cookie set() either matches the non-iframe's domain-scoped cookie (common on platforms like Replit where hostname == eTLD+1) or silently fails — both preferable to creating a conflicting host-only cookie

Test plan

  • Updated existing unit test for getCookieDomain to reflect new fallback behavior
  • Manual testing: load app in iframe on Replit, verify only domain-scoped __client_uat cookies are created (no host-only duplicates)
  • Manual testing: load app in standalone tab, verify cookie behavior unchanged

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed __client_uat cookie domain handling so it behaves correctly when the app loads in iframe and non-iframe contexts.
    • Added a deterministic fallback for cookie domain resolution (returns the hostname when a broader domain can't be derived) to avoid host-only cookie conflicts in restricted environments.
    • Ensured cookie SameSite and Secure attributes are consistently applied during domain probing.
  • Tests

    • Added/updated tests to verify attribute forwarding to the domain probe and the hostname fallback.

When an app is loaded in both an iframe and a standalone tab, getCookieDomain()
returns undefined in the iframe (eTLD+1 probe fails due to third-party cookie
restrictions), causing host-only cookies that conflict with domain-scoped cookies
from the non-iframe context. Fall back to hostname instead of undefined so the
cookie set either matches the non-iframe's domain-scoped cookie or silently fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@changeset-bot
Copy link

changeset-bot bot commented Feb 18, 2026

🦋 Changeset detected

Latest commit: b245c99

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

This PR includes changesets to release 3 packages
Name Type
@clerk/clerk-js Patch
@clerk/chrome-extension Patch
@clerk/expo 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 Feb 18, 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 Feb 18, 2026 7:36pm

Request Review

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 18, 2026

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: b245c99

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

Adds a changeset for a patch release and updates getCookieDomain to accept optional cookieAttributes (sameSite, secure). The eTLD+1 probe now applies those attributes to set/remove calls and the function falls back to and caches the original hostname when no eTLD+1 is found instead of returning undefined. Tests were updated to assert the hostname fallback and that cookie attributes are forwarded to the probe. clientUat cookie logic now calls getCookieDomain with sameSite and secure attributes.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: preventing duplicate __client_uat cookies in iframe contexts, which is the core problem being addressed by the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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.

// In restricted contexts (e.g. cross-origin iframes), the set() will silently
// fail — which is preferable to creating a host-only cookie that conflicts
// with domain-scoped cookies set by non-iframe contexts.
cachedETLDPlusOne = hostname;
Copy link
Member

Choose a reason for hiding this comment

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

Hmm. I think it makes sense, but I wonder if this could potentially set a cookie on a different subdomain that would still create 2 separate cookies in some cases. 🤔

The eTLD+1 probe in getCookieDomain() was using default cookie attributes
(SameSite=Lax) while the actual __client_uat cookie uses SameSite=None;
Secure in iframe contexts. This mismatch could cause the probe to fail
at a domain level where the actual cookie set would succeed, potentially
creating duplicate cookies on different subdomains.

Now getCookieDomain() accepts optional cookie attributes that are passed
through to the probe, and clientUat.ts forwards SameSite/Secure so the
probe accurately reflects the actual cookie behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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/clerk-js/src/core/auth/getCookieDomain.ts`:
- Around line 11-23: The exported function getCookieDomain lacks an explicit
TypeScript return type and its JSDoc is incomplete; update the declaration of
getCookieDomain to include an explicit return type (e.g., string | undefined)
and ensure cookieAttributes has precise types (sameSite?: 'Lax' | 'Strict' |
'None' | string; secure?: boolean), then expand the JSDoc for getCookieDomain to
include `@returns` describing the returned eTLD+1 or undefined, `@throws` describing
any errors thrown during the eTLD+1 probe (e.g., when cookie handling fails),
and an `@example` showing typical usage; reference the function name
getCookieDomain and the cookieAttributes param as the places to change.

Comment on lines +11 to +23
/**
* @param hostname - The hostname to determine the eTLD+1 for.
* @param cookieHandler - The cookie handler to use for the eTLD+1 probe.
* @param cookieAttributes - Optional cookie attributes (sameSite, secure) to use
* during the eTLD+1 probe. These should match the attributes that will be used
* when setting the actual cookie, so the probe accurately reflects whether a
* domain-scoped cookie can be set in the current context.
*/
export function getCookieDomain(
hostname = window.location.hostname,
cookieHandler = eTLDCookie,
cookieAttributes?: { sameSite?: string; secure?: 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:

cat -n packages/clerk-js/src/core/auth/getCookieDomain.ts

Repository: clerk/javascript

Length of output: 2746


Add explicit return type and complete JSDoc documentation for the exported getCookieDomain function.

This public API lacks an explicit return type annotation. Its JSDoc documentation is also incomplete, missing @returns, @throws, and @example tags required by the TypeScript and JSDoc guidelines.

Suggested patch
 /**
  * `@param` hostname - The hostname to determine the eTLD+1 for.
  * `@param` cookieHandler - The cookie handler to use for the eTLD+1 probe.
  * `@param` cookieAttributes - Optional cookie attributes (sameSite, secure) to use
  *   during the eTLD+1 probe. These should match the attributes that will be used
  *   when setting the actual cookie, so the probe accurately reflects whether a
  *   domain-scoped cookie can be set in the current context.
+ * `@returns` The resolved eTLD+1 domain to use for setting cookies.
+ * `@throws` If the cookie handler throws while probing domains.
+ * `@example`
+ * getCookieDomain('app.example.com', undefined, { sameSite: 'None', secure: true });
  */
 export function getCookieDomain(
   hostname = window.location.hostname,
   cookieHandler = eTLDCookie,
   cookieAttributes?: { sameSite?: string; secure?: boolean },
-) {
+): string {
📝 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
/**
* @param hostname - The hostname to determine the eTLD+1 for.
* @param cookieHandler - The cookie handler to use for the eTLD+1 probe.
* @param cookieAttributes - Optional cookie attributes (sameSite, secure) to use
* during the eTLD+1 probe. These should match the attributes that will be used
* when setting the actual cookie, so the probe accurately reflects whether a
* domain-scoped cookie can be set in the current context.
*/
export function getCookieDomain(
hostname = window.location.hostname,
cookieHandler = eTLDCookie,
cookieAttributes?: { sameSite?: string; secure?: boolean },
) {
/**
* `@param` hostname - The hostname to determine the eTLD+1 for.
* `@param` cookieHandler - The cookie handler to use for the eTLD+1 probe.
* `@param` cookieAttributes - Optional cookie attributes (sameSite, secure) to use
* during the eTLD+1 probe. These should match the attributes that will be used
* when setting the actual cookie, so the probe accurately reflects whether a
* domain-scoped cookie can be set in the current context.
* `@returns` The resolved eTLD+1 domain to use for setting cookies.
* `@throws` If the cookie handler throws while probing domains.
* `@example`
* getCookieDomain('app.example.com', undefined, { sameSite: 'None', secure: true });
*/
export function getCookieDomain(
hostname = window.location.hostname,
cookieHandler = eTLDCookie,
cookieAttributes?: { sameSite?: string; secure?: boolean },
): string {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/clerk-js/src/core/auth/getCookieDomain.ts` around lines 11 - 23, The
exported function getCookieDomain lacks an explicit TypeScript return type and
its JSDoc is incomplete; update the declaration of getCookieDomain to include an
explicit return type (e.g., string | undefined) and ensure cookieAttributes has
precise types (sameSite?: 'Lax' | 'Strict' | 'None' | string; secure?: boolean),
then expand the JSDoc for getCookieDomain to include `@returns` describing the
returned eTLD+1 or undefined, `@throws` describing any errors thrown during the
eTLD+1 probe (e.g., when cookie handling fails), and an `@example` showing typical
usage; reference the function name getCookieDomain and the cookieAttributes
param as the places to change.

@brkalow brkalow merged commit 78dc225 into main Feb 18, 2026
65 of 66 checks passed
@brkalow brkalow deleted the bryce/fix-iframe-client-uat-cookie-domain branch February 18, 2026 20:55
brkalow added a commit that referenced this pull request Feb 18, 2026
…xts (#7875)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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

Comments