Skip to content

Commit f1d9d34

Browse files
authored
fix(clerk-js): Ensure cookies get unset on sign out (#6368)
1 parent fa8f460 commit f1d9d34

File tree

7 files changed

+76
-19
lines changed

7 files changed

+76
-19
lines changed

.changeset/famous-news-judge.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
'@clerk/shared': patch
4+
---
5+
6+
Fixes an issue where cookies were not properly cleared on sign out when using non-default cookie attributes.

packages/clerk-js/src/core/auth/AuthCookieService.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ export class AuthCookieService {
6868
this.setClientUatCookieForDevelopmentInstances();
6969
});
7070

71+
eventBus.on(events.UserSignOut, () => this.handleSignOut());
72+
7173
this.refreshTokenOnFocus();
7274
this.startPollingForToken();
7375

@@ -212,6 +214,12 @@ export class AuthCookieService {
212214
// --------
213215
}
214216

217+
private handleSignOut() {
218+
this.activeCookie.remove();
219+
this.sessionCookie.remove();
220+
this.setClientUatCookieForDevelopmentInstances();
221+
}
222+
215223
/**
216224
* The below methods handle active context tracking (session and organization) to ensure
217225
* only tabs with matching context can update the session cookie.

packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,30 @@ describe('createSessionCookie', () => {
7171
expect(mockRemove).toHaveBeenCalledTimes(2);
7272
});
7373

74+
it('should remove cookies with the same attributes as set', () => {
75+
const cookieHandler = createSessionCookie(mockCookieSuffix);
76+
cookieHandler.set(mockToken);
77+
cookieHandler.remove();
78+
79+
const expectedAttributes = {
80+
sameSite: 'Lax',
81+
secure: true,
82+
partitioned: false,
83+
};
84+
85+
expect(mockSet).toHaveBeenCalledWith(mockToken, {
86+
expires: mockExpires,
87+
sameSite: 'Lax',
88+
secure: true,
89+
partitioned: false,
90+
});
91+
92+
expect(mockRemove).toHaveBeenCalledWith(expectedAttributes);
93+
expect(mockRemove).toHaveBeenCalledTimes(2);
94+
expect(mockRemove).toHaveBeenNthCalledWith(1, expectedAttributes);
95+
expect(mockRemove).toHaveBeenNthCalledWith(2, expectedAttributes);
96+
});
97+
7498
it('should get cookie value from suffixed cookie first, then fallback to non-suffixed', () => {
7599
mockGet.mockImplementationOnce(() => 'suffixed-value').mockImplementationOnce(() => 'non-suffixed-value');
76100

packages/clerk-js/src/core/auth/cookies/devBrowser.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ export type DevBrowserCookieHandler = {
1212
remove: () => void;
1313
};
1414

15+
const getCookieAttributes = (): { sameSite: string; secure: boolean } => {
16+
const sameSite = inCrossOriginIframe() ? 'None' : 'Lax';
17+
const secure = getSecureAttribute(sameSite);
18+
return { sameSite, secure };
19+
};
20+
1521
/**
1622
* Create a long-lived JS cookie to store the dev browser token
1723
* ONLY for development instances.
@@ -26,16 +32,16 @@ export const createDevBrowserCookie = (cookieSuffix: string): DevBrowserCookieHa
2632

2733
const set = (jwt: string) => {
2834
const expires = addYears(Date.now(), 1);
29-
const sameSite = inCrossOriginIframe() ? 'None' : 'Lax';
30-
const secure = getSecureAttribute(sameSite);
35+
const { sameSite, secure } = getCookieAttributes();
3136

3237
suffixedDevBrowserCookie.set(jwt, { expires, sameSite, secure });
3338
devBrowserCookie.set(jwt, { expires, sameSite, secure });
3439
};
3540

3641
const remove = () => {
37-
suffixedDevBrowserCookie.remove();
38-
devBrowserCookie.remove();
42+
const attributes = getCookieAttributes();
43+
suffixedDevBrowserCookie.remove(attributes);
44+
devBrowserCookie.remove(attributes);
3945
};
4046

4147
return {

packages/clerk-js/src/core/auth/cookies/session.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ export type SessionCookieHandler = {
1313
get: () => string | undefined;
1414
};
1515

16+
const getCookieAttributes = (): { sameSite: string; secure: boolean; partitioned: boolean } => {
17+
const sameSite = __BUILD_VARIANT_CHIPS__ ? 'None' : inCrossOriginIframe() ? 'None' : 'Lax';
18+
const secure = getSecureAttribute(sameSite);
19+
const partitioned = __BUILD_VARIANT_CHIPS__ && secure;
20+
return { sameSite, secure, partitioned };
21+
};
22+
1623
/**
1724
* Create a short-lived JS cookie to store the current user JWT.
1825
* The cookie is used by the Clerk backend SDKs to identify
@@ -23,15 +30,14 @@ export const createSessionCookie = (cookieSuffix: string): SessionCookieHandler
2330
const suffixedSessionCookie = createCookieHandler(getSuffixedCookieName(SESSION_COOKIE_NAME, cookieSuffix));
2431

2532
const remove = () => {
26-
sessionCookie.remove();
27-
suffixedSessionCookie.remove();
33+
const attributes = getCookieAttributes();
34+
sessionCookie.remove(attributes);
35+
suffixedSessionCookie.remove(attributes);
2836
};
2937

3038
const set = (token: string) => {
3139
const expires = addYears(Date.now(), 1);
32-
const sameSite = __BUILD_VARIANT_CHIPS__ ? 'None' : inCrossOriginIframe() ? 'None' : 'Lax';
33-
const secure = getSecureAttribute(sameSite);
34-
const partitioned = __BUILD_VARIANT_CHIPS__ && secure;
40+
const { sameSite, secure, partitioned } = getCookieAttributes();
3541

3642
// If setting Partitioned to true, remove the existing session cookies.
3743
// This is to avoid conflicts with the same cookie name without Partitioned attribute.

packages/clerk-js/src/core/clerk.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -485,10 +485,8 @@ export class Clerk implements ClerkInterface {
485485
const executeSignOut = async () => {
486486
const tracker = createBeforeUnloadTracker(this.#options.standardBrowser);
487487

488-
// Notify other tabs that user is signing out.
488+
// Notify other tabs that user is signing out and clean up cookies.
489489
eventBus.emit(events.UserSignOut, null);
490-
// Clean up cookies
491-
eventBus.emit(events.TokenUpdate, { token: null });
492490

493491
this.#setTransitiveState();
494492

packages/shared/src/cookie.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import Cookies from 'js-cookie';
22

3-
type LocationAttributes = {
4-
path?: string;
5-
domain?: string;
6-
};
7-
3+
/**
4+
* Creates helper methods for dealing with a specific cookie.
5+
*
6+
* @example
7+
* ```ts
8+
* const cookie = createCookieHandler('my_cookie')
9+
*
10+
* cookie.set('my_value');
11+
* cookie.get() // 'my_value';
12+
* cookie.remove()
13+
* ```
14+
*/
815
export function createCookieHandler(cookieName: string) {
916
return {
1017
get() {
@@ -18,10 +25,12 @@ export function createCookieHandler(cookieName: string) {
1825
},
1926
/**
2027
* On removing a cookie, you have to pass the exact same path/domain attributes used to set it initially
28+
* > 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.
29+
*
2130
* @see https://github.com/js-cookie/js-cookie#basic-usage
2231
*/
23-
remove(locationAttributes?: LocationAttributes) {
24-
Cookies.remove(cookieName, locationAttributes);
32+
remove(cookieAttributes?: Cookies.CookieAttributes) {
33+
Cookies.remove(cookieName, cookieAttributes);
2534
},
2635
};
2736
}

0 commit comments

Comments
 (0)