From e8dc4c2f7006174a3e6c057e32f65f70a3491e24 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 21 Jul 2025 12:04:34 -0500 Subject: [PATCH 01/10] Ensure same cookie attributes are passed to remove() and fixes the types --- .../src/core/auth/AuthCookieService.ts | 8 +++++++ .../src/core/auth/cookies/devBrowser.ts | 15 ++++++++---- .../clerk-js/src/core/auth/cookies/session.ts | 16 +++++++++---- packages/clerk-js/src/core/clerk.ts | 2 -- packages/shared/src/cookie.ts | 23 +++++++++++++------ 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/packages/clerk-js/src/core/auth/AuthCookieService.ts b/packages/clerk-js/src/core/auth/AuthCookieService.ts index dcdba03d856..6cbdd8207e7 100644 --- a/packages/clerk-js/src/core/auth/AuthCookieService.ts +++ b/packages/clerk-js/src/core/auth/AuthCookieService.ts @@ -68,6 +68,8 @@ export class AuthCookieService { this.setClientUatCookieForDevelopmentInstances(); }); + eventBus.on(events.UserSignOut, () => this.handleSignOut()); + this.refreshTokenOnFocus(); this.startPollingForToken(); @@ -212,6 +214,12 @@ export class AuthCookieService { // -------- } + private handleSignOut() { + this.activeCookie.remove(); + this.sessionCookie.remove(); + this.setClientUatCookieForDevelopmentInstances(); + } + /** * The below methods handle active context tracking (session and organization) to ensure * only tabs with matching context can update the session cookie. diff --git a/packages/clerk-js/src/core/auth/cookies/devBrowser.ts b/packages/clerk-js/src/core/auth/cookies/devBrowser.ts index 7b292a9e656..32733a96b62 100644 --- a/packages/clerk-js/src/core/auth/cookies/devBrowser.ts +++ b/packages/clerk-js/src/core/auth/cookies/devBrowser.ts @@ -3,7 +3,6 @@ import { addYears } from '@clerk/shared/date'; import { DEV_BROWSER_JWT_KEY } from '@clerk/shared/devBrowser'; import { getSuffixedCookieName } from '@clerk/shared/keys'; -import { inCrossOriginIframe } from '../../../utils'; import { getSecureAttribute } from '../getSecureAttribute'; export type DevBrowserCookieHandler = { @@ -12,6 +11,12 @@ export type DevBrowserCookieHandler = { remove: () => void; }; +const getCookieAttributes = (): { sameSite: string; secure: boolean } => { + const sameSite = 'None'; + const secure = getSecureAttribute(sameSite); + return { sameSite, secure }; +}; + /** * Create a long-lived JS cookie to store the dev browser token * ONLY for development instances. @@ -26,16 +31,16 @@ export const createDevBrowserCookie = (cookieSuffix: string): DevBrowserCookieHa const set = (jwt: string) => { const expires = addYears(Date.now(), 1); - const sameSite = inCrossOriginIframe() ? 'None' : 'Lax'; - const secure = getSecureAttribute(sameSite); + const { sameSite, secure } = getCookieAttributes(); suffixedDevBrowserCookie.set(jwt, { expires, sameSite, secure }); devBrowserCookie.set(jwt, { expires, sameSite, secure }); }; const remove = () => { - suffixedDevBrowserCookie.remove(); - devBrowserCookie.remove(); + const attributes = getCookieAttributes(); + suffixedDevBrowserCookie.remove(attributes); + devBrowserCookie.remove(attributes); }; return { diff --git a/packages/clerk-js/src/core/auth/cookies/session.ts b/packages/clerk-js/src/core/auth/cookies/session.ts index 8025b69aa8b..209755d96f4 100644 --- a/packages/clerk-js/src/core/auth/cookies/session.ts +++ b/packages/clerk-js/src/core/auth/cookies/session.ts @@ -13,6 +13,13 @@ export type SessionCookieHandler = { get: () => string | undefined; }; +const getCookieAttributes = (): { sameSite: string; secure: boolean; partitioned: boolean } => { + const sameSite = __BUILD_VARIANT_CHIPS__ ? 'None' : inCrossOriginIframe() ? 'None' : 'Lax'; + const secure = getSecureAttribute(sameSite); + const partitioned = __BUILD_VARIANT_CHIPS__ && secure; + return { sameSite, secure, partitioned }; +}; + /** * Create a short-lived JS cookie to store the current user JWT. * The cookie is used by the Clerk backend SDKs to identify @@ -23,15 +30,14 @@ export const createSessionCookie = (cookieSuffix: string): SessionCookieHandler const suffixedSessionCookie = createCookieHandler(getSuffixedCookieName(SESSION_COOKIE_NAME, cookieSuffix)); const remove = () => { - sessionCookie.remove(); - suffixedSessionCookie.remove(); + const attributes = getCookieAttributes(); + sessionCookie.remove(attributes); + suffixedSessionCookie.remove(attributes); }; const set = (token: string) => { const expires = addYears(Date.now(), 1); - const sameSite = __BUILD_VARIANT_CHIPS__ ? 'None' : inCrossOriginIframe() ? 'None' : 'Lax'; - const secure = getSecureAttribute(sameSite); - const partitioned = __BUILD_VARIANT_CHIPS__ && secure; + const { sameSite, secure, partitioned } = getCookieAttributes(); // If setting Partitioned to true, remove the existing session cookies. // This is to avoid conflicts with the same cookie name without Partitioned attribute. diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 8d1f5450c43..f3a13254de6 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -485,8 +485,6 @@ export class Clerk implements ClerkInterface { // Notify other tabs that user is signing out. eventBus.emit(events.UserSignOut, null); - // Clean up cookies - eventBus.emit(events.TokenUpdate, { token: null }); this.#setTransitiveState(); diff --git a/packages/shared/src/cookie.ts b/packages/shared/src/cookie.ts index 87679a095a9..9e726ed2ea9 100644 --- a/packages/shared/src/cookie.ts +++ b/packages/shared/src/cookie.ts @@ -1,10 +1,17 @@ import Cookies from 'js-cookie'; -type LocationAttributes = { - path?: string; - domain?: string; -}; - +/** + * Creates helper methods for dealing with a specific cookie. + * + * @example + * ```ts + * const cookie = createCookieHandler('my_cookie') + * + * cookie.set('my_value'); + * cookie.get() // 'my_value'; + * cookie.remove() + * ``` + */ export function createCookieHandler(cookieName: string) { return { get() { @@ -18,10 +25,12 @@ export function createCookieHandler(cookieName: string) { }, /** * On removing a cookie, you have to pass the exact same path/domain attributes used to set it initially + * > IMPORTANT! When deleting a cookie and you're not relying on the default attributes, you must pass the exact same path, domain, secure and sameSite attributes that were used to set the cookie. + * * @see https://github.com/js-cookie/js-cookie#basic-usage */ - remove(locationAttributes?: LocationAttributes) { - Cookies.remove(cookieName, locationAttributes); + remove(cookieAttributes?: Cookies.CookieAttributes) { + Cookies.remove(cookieName, cookieAttributes); }, }; } From 6f20ed87e119071fbd2c07235ac3ef7834dba3eb Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 21 Jul 2025 12:22:24 -0500 Subject: [PATCH 02/10] Adds test case --- .../auth/cookies/__tests__/session.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts b/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts index 8ca14d5a5e9..3ab6bca3966 100644 --- a/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts +++ b/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts @@ -71,6 +71,30 @@ describe('createSessionCookie', () => { expect(mockRemove).toHaveBeenCalledTimes(2); }); + it('should remove cookies with the same attributes as set', () => { + const cookieHandler = createSessionCookie(mockCookieSuffix); + cookieHandler.set(mockToken); + cookieHandler.remove(); + + const expectedAttributes = { + sameSite: 'Lax', + secure: true, + partitioned: false, + }; + + expect(mockSet).toHaveBeenCalledWith(mockToken, { + expires: mockExpires, + sameSite: 'Lax', + secure: true, + partitioned: false, + }); + + expect(mockRemove).toHaveBeenCalledWith(expectedAttributes); + expect(mockRemove).toHaveBeenCalledTimes(2); + expect(mockRemove).toHaveBeenNthCalledWith(1, expectedAttributes); + expect(mockRemove).toHaveBeenNthCalledWith(2, expectedAttributes); + }); + it('should get cookie value from suffixed cookie first, then fallback to non-suffixed', () => { mockGet.mockImplementationOnce(() => 'suffixed-value').mockImplementationOnce(() => 'non-suffixed-value'); From 81b7b4af8ca9e4aea5321250e75a96da7ffdb0c9 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 21 Jul 2025 12:23:20 -0500 Subject: [PATCH 03/10] Adds changeset --- .changeset/famous-news-judge.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/famous-news-judge.md diff --git a/.changeset/famous-news-judge.md b/.changeset/famous-news-judge.md new file mode 100644 index 00000000000..28efd16bc2a --- /dev/null +++ b/.changeset/famous-news-judge.md @@ -0,0 +1,6 @@ +--- +'@clerk/clerk-js': patch +'@clerk/shared': patch +--- + +Fixes an issue where cookies were not properly cleared on sign out when using non-default cookie attributes. From b5bc084d878eeb0db4c0fd418b5a0bd79904ba2b Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 21 Jul 2025 13:02:29 -0500 Subject: [PATCH 04/10] Apply suggestion from @brkalow --- packages/clerk-js/src/core/auth/cookies/devBrowser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/clerk-js/src/core/auth/cookies/devBrowser.ts b/packages/clerk-js/src/core/auth/cookies/devBrowser.ts index 32733a96b62..4656b4e3a82 100644 --- a/packages/clerk-js/src/core/auth/cookies/devBrowser.ts +++ b/packages/clerk-js/src/core/auth/cookies/devBrowser.ts @@ -3,6 +3,7 @@ import { addYears } from '@clerk/shared/date'; import { DEV_BROWSER_JWT_KEY } from '@clerk/shared/devBrowser'; import { getSuffixedCookieName } from '@clerk/shared/keys'; +import { inCrossOriginIframe } from '../../../utils'; import { getSecureAttribute } from '../getSecureAttribute'; export type DevBrowserCookieHandler = { From 7cfeed278066c4cd7c4ae17a5618efa075b2a781 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 21 Jul 2025 13:02:35 -0500 Subject: [PATCH 05/10] Apply suggestion from @brkalow --- packages/clerk-js/src/core/auth/cookies/devBrowser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/src/core/auth/cookies/devBrowser.ts b/packages/clerk-js/src/core/auth/cookies/devBrowser.ts index 4656b4e3a82..47edda5fadd 100644 --- a/packages/clerk-js/src/core/auth/cookies/devBrowser.ts +++ b/packages/clerk-js/src/core/auth/cookies/devBrowser.ts @@ -13,7 +13,7 @@ export type DevBrowserCookieHandler = { }; const getCookieAttributes = (): { sameSite: string; secure: boolean } => { - const sameSite = 'None'; + const sameSite = inCrossOriginIframe() ? 'None' : 'Lax'; const secure = getSecureAttribute(sameSite); return { sameSite, secure }; }; From fb9e5903e69e6e04388b63b9e008bcdb81ae3330 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Tue, 22 Jul 2025 14:52:23 -0500 Subject: [PATCH 06/10] Use SameSite=none by default for development instances --- .../src/core/auth/AuthCookieService.ts | 4 +-- .../src/core/auth/cookies/clientUat.ts | 16 +++++++++-- .../src/core/auth/cookies/devBrowser.ts | 9 ++++-- .../clerk-js/src/core/auth/cookies/session.ts | 28 ++++++++++++------- packages/clerk-js/src/core/auth/devBrowser.ts | 2 +- 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/packages/clerk-js/src/core/auth/AuthCookieService.ts b/packages/clerk-js/src/core/auth/AuthCookieService.ts index 6cbdd8207e7..8c5e59a32e8 100644 --- a/packages/clerk-js/src/core/auth/AuthCookieService.ts +++ b/packages/clerk-js/src/core/auth/AuthCookieService.ts @@ -73,8 +73,8 @@ export class AuthCookieService { this.refreshTokenOnFocus(); this.startPollingForToken(); - this.clientUat = createClientUatCookie(cookieSuffix); - this.sessionCookie = createSessionCookie(cookieSuffix); + this.clientUat = createClientUatCookie({ cookieSuffix, isProduction: this.instanceType === 'production' }); + this.sessionCookie = createSessionCookie({ cookieSuffix, isProduction: this.instanceType === 'production' }); this.activeCookie = createCookieHandler('clerk_active_context'); this.devBrowser = createDevBrowser({ frontendApi: clerk.frontendApi, diff --git a/packages/clerk-js/src/core/auth/cookies/clientUat.ts b/packages/clerk-js/src/core/auth/cookies/clientUat.ts index 9ffec6e1456..d2d4dd8d6a8 100644 --- a/packages/clerk-js/src/core/auth/cookies/clientUat.ts +++ b/packages/clerk-js/src/core/auth/cookies/clientUat.ts @@ -20,7 +20,13 @@ export type ClientUatCookieHandler = { * The cookie is used as hint from the Clerk Backend packages to identify * if the user is authenticated or not. */ -export const createClientUatCookie = (cookieSuffix: string): ClientUatCookieHandler => { +export const createClientUatCookie = ({ + cookieSuffix, + isProduction, +}: { + cookieSuffix: string; + isProduction: boolean; +}): ClientUatCookieHandler => { const clientUatCookie = createCookieHandler(CLIENT_UAT_COOKIE_NAME); const suffixedClientUatCookie = createCookieHandler(getSuffixedCookieName(CLIENT_UAT_COOKIE_NAME, cookieSuffix)); @@ -37,7 +43,13 @@ export const createClientUatCookie = (cookieSuffix: string): ClientUatCookieHand * Generally, this is handled by redirectWithAuth() being called and relying on the dev browser ID in the URL, * but if that isn't used we rely on this. In production, nothing is cross-domain and Lax is used when client_uat is set from FAPI. */ - const sameSite = __BUILD_VARIANT_CHIPS__ ? 'None' : inCrossOriginIframe() ? 'None' : 'Strict'; + const sameSite = __BUILD_VARIANT_CHIPS__ + ? 'None' + : inCrossOriginIframe() + ? 'None' + : isProduction + ? 'Strict' + : 'None'; const secure = getSecureAttribute(sameSite); const partitioned = __BUILD_VARIANT_CHIPS__ && secure; const domain = getCookieDomain(); diff --git a/packages/clerk-js/src/core/auth/cookies/devBrowser.ts b/packages/clerk-js/src/core/auth/cookies/devBrowser.ts index 47edda5fadd..0649ed7290b 100644 --- a/packages/clerk-js/src/core/auth/cookies/devBrowser.ts +++ b/packages/clerk-js/src/core/auth/cookies/devBrowser.ts @@ -3,7 +3,6 @@ import { addYears } from '@clerk/shared/date'; import { DEV_BROWSER_JWT_KEY } from '@clerk/shared/devBrowser'; import { getSuffixedCookieName } from '@clerk/shared/keys'; -import { inCrossOriginIframe } from '../../../utils'; import { getSecureAttribute } from '../getSecureAttribute'; export type DevBrowserCookieHandler = { @@ -13,7 +12,7 @@ export type DevBrowserCookieHandler = { }; const getCookieAttributes = (): { sameSite: string; secure: boolean } => { - const sameSite = inCrossOriginIframe() ? 'None' : 'Lax'; + const sameSite = 'None'; const secure = getSecureAttribute(sameSite); return { sameSite, secure }; }; @@ -24,7 +23,7 @@ const getCookieAttributes = (): { sameSite: string; secure: boolean } => { * The cookie is used to authenticate FAPI requests and pass * authentication from AP to the app. */ -export const createDevBrowserCookie = (cookieSuffix: string): DevBrowserCookieHandler => { +export const createDevBrowserCookie = ({ cookieSuffix }: { cookieSuffix: string }): DevBrowserCookieHandler => { const devBrowserCookie = createCookieHandler(DEV_BROWSER_JWT_KEY); const suffixedDevBrowserCookie = createCookieHandler(getSuffixedCookieName(DEV_BROWSER_JWT_KEY, cookieSuffix)); @@ -34,6 +33,10 @@ export const createDevBrowserCookie = (cookieSuffix: string): DevBrowserCookieHa const expires = addYears(Date.now(), 1); const { sameSite, secure } = getCookieAttributes(); + // Remove existing cookies to prevent conflicts when SameSite attributes change + devBrowserCookie.remove(); + suffixedDevBrowserCookie.remove(); + suffixedDevBrowserCookie.set(jwt, { expires, sameSite, secure }); devBrowserCookie.set(jwt, { expires, sameSite, secure }); }; diff --git a/packages/clerk-js/src/core/auth/cookies/session.ts b/packages/clerk-js/src/core/auth/cookies/session.ts index 209755d96f4..32cd27a8e7a 100644 --- a/packages/clerk-js/src/core/auth/cookies/session.ts +++ b/packages/clerk-js/src/core/auth/cookies/session.ts @@ -13,8 +13,12 @@ export type SessionCookieHandler = { get: () => string | undefined; }; -const getCookieAttributes = (): { sameSite: string; secure: boolean; partitioned: boolean } => { - const sameSite = __BUILD_VARIANT_CHIPS__ ? 'None' : inCrossOriginIframe() ? 'None' : 'Lax'; +const getCookieAttributes = ({ + isProduction, +}: { + isProduction: boolean; +}): { sameSite: string; secure: boolean; partitioned: boolean } => { + const sameSite = __BUILD_VARIANT_CHIPS__ ? 'None' : inCrossOriginIframe() ? 'None' : isProduction ? 'Lax' : 'None'; const secure = getSecureAttribute(sameSite); const partitioned = __BUILD_VARIANT_CHIPS__ && secure; return { sameSite, secure, partitioned }; @@ -25,25 +29,29 @@ const getCookieAttributes = (): { sameSite: string; secure: boolean; partitioned * The cookie is used by the Clerk backend SDKs to identify * the authenticated user. */ -export const createSessionCookie = (cookieSuffix: string): SessionCookieHandler => { +export const createSessionCookie = ({ + cookieSuffix, + isProduction, +}: { + cookieSuffix: string; + isProduction: boolean; +}): SessionCookieHandler => { const sessionCookie = createCookieHandler(SESSION_COOKIE_NAME); const suffixedSessionCookie = createCookieHandler(getSuffixedCookieName(SESSION_COOKIE_NAME, cookieSuffix)); const remove = () => { - const attributes = getCookieAttributes(); + const attributes = getCookieAttributes({ isProduction }); sessionCookie.remove(attributes); suffixedSessionCookie.remove(attributes); }; const set = (token: string) => { const expires = addYears(Date.now(), 1); - const { sameSite, secure, partitioned } = getCookieAttributes(); + const { sameSite, secure, partitioned } = getCookieAttributes({ isProduction }); - // If setting Partitioned to true, remove the existing session cookies. - // This is to avoid conflicts with the same cookie name without Partitioned attribute. - if (partitioned) { - remove(); - } + // Remove existing cookies to prevent conflicts when SameSite attributes change + sessionCookie.remove(); + suffixedSessionCookie.remove(); sessionCookie.set(token, { expires, sameSite, secure, partitioned }); suffixedSessionCookie.set(token, { expires, sameSite, secure, partitioned }); diff --git a/packages/clerk-js/src/core/auth/devBrowser.ts b/packages/clerk-js/src/core/auth/devBrowser.ts index 2d5e3cb644e..7b7527e3709 100644 --- a/packages/clerk-js/src/core/auth/devBrowser.ts +++ b/packages/clerk-js/src/core/auth/devBrowser.ts @@ -26,7 +26,7 @@ export type CreateDevBrowserOptions = { }; export function createDevBrowser({ cookieSuffix, frontendApi, fapiClient }: CreateDevBrowserOptions): DevBrowser { - const devBrowserCookie = createDevBrowserCookie(cookieSuffix); + const devBrowserCookie = createDevBrowserCookie({ cookieSuffix }); function getDevBrowserJWT() { return devBrowserCookie.get(); From 3f2a3912641a0808940d22b4773abada2c60d050 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Tue, 22 Jul 2025 16:27:27 -0500 Subject: [PATCH 07/10] Updates test cases --- .../auth/cookies/__tests__/session.test.ts | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts b/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts index 3ab6bca3966..9d9c3b57609 100644 --- a/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts +++ b/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts @@ -32,30 +32,17 @@ describe('createSessionCookie', () => { }); it('should create both suffixed and non-suffixed cookie handlers', () => { - createSessionCookie(mockCookieSuffix); + createSessionCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); expect(createCookieHandler).toHaveBeenCalledTimes(2); expect(createCookieHandler).toHaveBeenCalledWith('__session'); expect(createCookieHandler).toHaveBeenCalledWith('__session_test-suffix'); }); - it('should set cookies with correct parameters in non-cross-origin context', () => { - const cookieHandler = createSessionCookie(mockCookieSuffix); + it('should set cookies with correct parameters', () => { + const cookieHandler = createSessionCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); cookieHandler.set(mockToken); expect(mockSet).toHaveBeenCalledTimes(2); - expect(mockSet).toHaveBeenCalledWith(mockToken, { - expires: mockExpires, - sameSite: 'Lax', - secure: true, - partitioned: false, - }); - }); - - it('should set cookies with None sameSite in cross-origin context', () => { - (inCrossOriginIframe as jest.Mock).mockReturnValue(true); - const cookieHandler = createSessionCookie(mockCookieSuffix); - cookieHandler.set(mockToken); - expect(mockSet).toHaveBeenCalledWith(mockToken, { expires: mockExpires, sameSite: 'None', @@ -65,40 +52,37 @@ describe('createSessionCookie', () => { }); it('should remove both cookies when remove is called', () => { - const cookieHandler = createSessionCookie(mockCookieSuffix); + const cookieHandler = createSessionCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); cookieHandler.remove(); expect(mockRemove).toHaveBeenCalledTimes(2); }); it('should remove cookies with the same attributes as set', () => { - const cookieHandler = createSessionCookie(mockCookieSuffix); + const cookieHandler = createSessionCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); cookieHandler.set(mockToken); cookieHandler.remove(); const expectedAttributes = { - sameSite: 'Lax', + sameSite: 'None', secure: true, partitioned: false, }; expect(mockSet).toHaveBeenCalledWith(mockToken, { expires: mockExpires, - sameSite: 'Lax', + sameSite: 'None', secure: true, partitioned: false, }); expect(mockRemove).toHaveBeenCalledWith(expectedAttributes); - expect(mockRemove).toHaveBeenCalledTimes(2); - expect(mockRemove).toHaveBeenNthCalledWith(1, expectedAttributes); - expect(mockRemove).toHaveBeenNthCalledWith(2, expectedAttributes); }); it('should get cookie value from suffixed cookie first, then fallback to non-suffixed', () => { mockGet.mockImplementationOnce(() => 'suffixed-value').mockImplementationOnce(() => 'non-suffixed-value'); - const cookieHandler = createSessionCookie(mockCookieSuffix); + const cookieHandler = createSessionCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); const result = cookieHandler.get(); expect(result).toBe('suffixed-value'); @@ -107,9 +91,21 @@ describe('createSessionCookie', () => { it('should fallback to non-suffixed cookie when suffixed cookie is not present', () => { mockGet.mockImplementationOnce(() => undefined).mockImplementationOnce(() => 'non-suffixed-value'); - const cookieHandler = createSessionCookie(mockCookieSuffix); + const cookieHandler = createSessionCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); const result = cookieHandler.get(); expect(result).toBe('non-suffixed-value'); }); + + it('should not set SameSite=None in production', () => { + const cookieHandler = createSessionCookie({ cookieSuffix: mockCookieSuffix, isProduction: true }); + cookieHandler.set(mockToken); + + expect(mockSet).toHaveBeenCalledWith(mockToken, { + expires: mockExpires, + sameSite: 'Lax', + secure: true, + partitioned: false, + }); + }); }); From 1e6bb8bd9bee4b6f61e9c8724a78336a3732bbb4 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Tue, 22 Jul 2025 16:32:55 -0500 Subject: [PATCH 08/10] updates tests --- .../auth/cookies/__tests__/clientUat.test.ts | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/packages/clerk-js/src/core/auth/cookies/__tests__/clientUat.test.ts b/packages/clerk-js/src/core/auth/cookies/__tests__/clientUat.test.ts index c8dd0633d7e..6afb241ce1a 100644 --- a/packages/clerk-js/src/core/auth/cookies/__tests__/clientUat.test.ts +++ b/packages/clerk-js/src/core/auth/cookies/__tests__/clientUat.test.ts @@ -35,14 +35,14 @@ describe('createClientUatCookie', () => { }); it('should create both suffixed and non-suffixed cookie handlers', () => { - createClientUatCookie(mockCookieSuffix); + createClientUatCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); expect(createCookieHandler).toHaveBeenCalledTimes(2); expect(createCookieHandler).toHaveBeenCalledWith('__client_uat'); expect(createCookieHandler).toHaveBeenCalledWith('__client_uat_test-suffix'); }); it('should set cookies with correct parameters in non-cross-origin context', () => { - const cookieHandler = createClientUatCookie(mockCookieSuffix); + const cookieHandler = createClientUatCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); cookieHandler.set({ id: 'test-client', updatedAt: new Date('2024-01-01'), @@ -50,24 +50,6 @@ describe('createClientUatCookie', () => { }); expect(mockSet).toHaveBeenCalledTimes(2); - expect(mockSet).toHaveBeenCalledWith('1704067200', { - domain: mockDomain, - expires: mockExpires, - sameSite: 'Strict', - secure: true, - partitioned: false, - }); - }); - - it('should set cookies with None sameSite in cross-origin context', () => { - (inCrossOriginIframe as jest.Mock).mockReturnValue(true); - const cookieHandler = createClientUatCookie(mockCookieSuffix); - cookieHandler.set({ - id: 'test-client', - updatedAt: new Date('2024-01-01'), - signedInSessions: ['session1'], - }); - expect(mockSet).toHaveBeenCalledWith('1704067200', { domain: mockDomain, expires: mockExpires, @@ -78,20 +60,20 @@ describe('createClientUatCookie', () => { }); it('should set value to 0 when client is undefined', () => { - const cookieHandler = createClientUatCookie(mockCookieSuffix); + const cookieHandler = createClientUatCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); cookieHandler.set(undefined); expect(mockSet).toHaveBeenCalledWith('0', { domain: mockDomain, expires: mockExpires, - sameSite: 'Strict', + sameSite: 'None', secure: true, partitioned: false, }); }); it('should set value to 0 when client has no signed in sessions', () => { - const cookieHandler = createClientUatCookie(mockCookieSuffix); + const cookieHandler = createClientUatCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); cookieHandler.set({ id: 'test-client', updatedAt: new Date('2024-01-01'), @@ -101,7 +83,7 @@ describe('createClientUatCookie', () => { expect(mockSet).toHaveBeenCalledWith('0', { domain: mockDomain, expires: mockExpires, - sameSite: 'Strict', + sameSite: 'None', secure: true, partitioned: false, }); @@ -110,7 +92,7 @@ describe('createClientUatCookie', () => { it('should get cookie value from suffixed cookie first, then fallback to non-suffixed', () => { mockGet.mockImplementationOnce(() => '1234567890').mockImplementationOnce(() => '9876543210'); - const cookieHandler = createClientUatCookie(mockCookieSuffix); + const cookieHandler = createClientUatCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); const result = cookieHandler.get(); expect(result).toBe(1234567890); @@ -119,9 +101,26 @@ describe('createClientUatCookie', () => { it('should return 0 when no cookie value is present', () => { mockGet.mockImplementationOnce(() => undefined).mockImplementationOnce(() => undefined); - const cookieHandler = createClientUatCookie(mockCookieSuffix); + const cookieHandler = createClientUatCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); const result = cookieHandler.get(); expect(result).toBe(0); }); + + it('should set SameSite=Strict in production', () => { + const cookieHandler = createClientUatCookie({ cookieSuffix: mockCookieSuffix, isProduction: true }); + cookieHandler.set({ + id: 'test-client', + updatedAt: new Date('2024-01-01'), + signedInSessions: ['session1'], + }); + + expect(mockSet).toHaveBeenCalledWith('1704067200', { + domain: mockDomain, + expires: mockExpires, + sameSite: 'Strict', + secure: true, + partitioned: false, + }); + }); }); From b014d88fece35a33da557afead5aebc19e61792f Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Tue, 22 Jul 2025 16:37:31 -0500 Subject: [PATCH 09/10] adds changeset --- .changeset/great-rats-argue.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/great-rats-argue.md diff --git a/.changeset/great-rats-argue.md b/.changeset/great-rats-argue.md new file mode 100644 index 00000000000..e758c90aff4 --- /dev/null +++ b/.changeset/great-rats-argue.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': minor +--- + +Adjusts cookie setting in development so Clerk can function in embedded iframe scenarios. From 33307eeff25974c861094ddf0b77fa64146360b6 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Mon, 28 Jul 2025 15:49:54 -0500 Subject: [PATCH 10/10] Adjust test case --- .../src/core/auth/cookies/__tests__/session.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts b/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts index 5619dd2cec4..1385b11e57d 100644 --- a/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts +++ b/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts @@ -80,19 +80,19 @@ describe('createSessionCookie', () => { }); it('should remove cookies with the same attributes as set', () => { - const cookieHandler = createSessionCookie(mockCookieSuffix); + const cookieHandler = createSessionCookie({ cookieSuffix: mockCookieSuffix, isProduction: false }); cookieHandler.set(mockToken); cookieHandler.remove(); const expectedAttributes = { - sameSite: 'Lax', + sameSite: 'None', secure: true, partitioned: false, }; expect(mockSet).toHaveBeenCalledWith(mockToken, { expires: mockExpires, - sameSite: 'Lax', + sameSite: 'None', secure: true, partitioned: false, });