diff --git a/.changeset/fluffy-turtles-applaud.md b/.changeset/fluffy-turtles-applaud.md new file mode 100644 index 00000000000..f9dd5e8d3b9 --- /dev/null +++ b/.changeset/fluffy-turtles-applaud.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Remove cookie when signing out before running `onBeforeSetActive` to resolve issues where we do navigations in `onBeforeSetActive`. diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts index b18787dda80..97b33b49d4f 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts @@ -224,6 +224,25 @@ describe('Clerk singleton', () => { expect(mockSession.touch).toHaveBeenCalled(); }); + /** + * The __session cookie needs to be cleared before calling __unstable__onBeforeSetActive + * as the callback may rely on the absence of the cookie to determine the user is logged out or not + * For example, for NextJS integration, calling __unstable__onBeforeSetActive before clearing the cookie + * would result in hitting the middleware with a valid session cookie (until it expires), even if the session no longer exists + */ + it('clears __session cookie before calling __unstable__onBeforeSetActive', async () => { + mockSession.touch.mockReturnValueOnce(Promise.resolve()); + mockClientFetch.mockReturnValue(Promise.resolve({ activeSessions: [mockSession] })); + + (window as any).__unstable__onBeforeSetActive = () => { + expect(evenBusSpy).toHaveBeenCalledWith('token:update', { token: null }); + }; + + const sut = new Clerk(productionPublishableKey); + await sut.load(); + await sut.setActive({ session: null }); + }); + it('calls __unstable__onAfterSetActive after beforeEmit and session.touch', async () => { const beforeEmitMock = jest.fn(); mockSession.touch.mockReturnValueOnce(Promise.resolve()); diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 855d57a6072..90a97ab86f2 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -728,6 +728,7 @@ export class Clerk implements ClerkInterface { const shouldSignOutSession = this.session && newSession === null; if (shouldSignOutSession) { this.#broadcastSignOutEvent(); + eventBus.dispatch(events.TokenUpdate, { token: null }); } await onBeforeSetActive(); @@ -741,13 +742,9 @@ export class Clerk implements ClerkInterface { newSession = this.#getSessionFromClient(newSession?.id); } - // Sync __session and __client_uat to cookies using events.TokenUpdate dispatched event - // only for newSession is null since the getToken will not be executed. Since getToken - // triggers internally a events.TokenUpdate there is no need to trigger it when the newSession exists. - const token = await newSession?.getToken(); - if (!token) { - eventBus.dispatch(events.TokenUpdate, { token: null }); - } + // getToken syncs __session and __client_uat to cookies using events.TokenUpdate dispatched event. + await newSession?.getToken(); + //2. If there's a beforeEmit, typically we're navigating. Emit the session as // undefined, then wait for beforeEmit to complete before emitting the new session. // When undefined, neither SignedIn nor SignedOut renders, which avoids flickers or