Skip to content

fix(clerk-js): Preserve active organization when attempting to switch to personal workspace #6103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dark-cougars-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Maintain current active organization when `setActive({ organization: null })` is called with force organization selection enabled
81 changes: 81 additions & 0 deletions packages/clerk-js/src/core/__tests__/clerk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,87 @@ describe('Clerk singleton', () => {
});
});
});

describe('with force organization selection enabled', () => {
const mockSession = {
id: '1',
remove: jest.fn(),
status: 'active',
user: {},
touch: jest.fn(() => Promise.resolve()),
getToken: jest.fn(),
lastActiveToken: { getRawString: () => 'mocked-token' },
};

beforeEach(() => {
mockEnvironmentFetch.mockReturnValue(
Promise.resolve({
userSettings: mockUserSettings,
displayConfig: mockDisplayConfig,
isSingleSession: () => false,
isProduction: () => false,
isDevelopmentOrStaging: () => true,
organizationSettings: {
forceOrganizationSelection: true,
},
}),
);
});

afterEach(() => {
mockSession.remove.mockReset();
mockSession.touch.mockReset();
mockEnvironmentFetch.mockReset();

// cleanup global window pollution
(window as any).__unstable__onBeforeSetActive = null;
(window as any).__unstable__onAfterSetActive = null;
});

it('does not update session to personal workspace', async () => {
const mockSessionWithOrganization = {
id: '1',
status: 'active',
user: {
organizationMemberships: [
{
id: 'orgmem_id',
organization: {
id: 'org_id',
slug: 'some-org-slug',
},
},
],
},
touch: jest.fn(),
getToken: jest.fn(),
};

mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSessionWithOrganization] }));
const sut = new Clerk(productionPublishableKey);
await sut.load();

mockSessionWithOrganization.touch.mockImplementationOnce(() => {
sut.session = mockSessionWithOrganization as any;
return Promise.resolve();
});
mockSessionWithOrganization.getToken.mockImplementation(() => 'mocked-token');

await sut.setActive({ organization: 'some-org-slug' });

await waitFor(() => {
expect(mockSessionWithOrganization.touch).toHaveBeenCalled();
expect(mockSessionWithOrganization.getToken).toHaveBeenCalled();
expect((mockSessionWithOrganization as any as ActiveSessionResource)?.lastActiveOrganizationId).toEqual(
'org_id',
);
expect(sut.session).toMatchObject(mockSessionWithOrganization);
});

await sut.setActive({ organization: null });
expect(sut.session).toMatchObject(mockSessionWithOrganization);
});
});
});

describe('.load()', () => {
Expand Down
15 changes: 10 additions & 5 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1071,10 +1071,6 @@ export class Clerk implements ClerkInterface {
);
}

if (organization === null && this.environment?.organizationSettings?.forceOrganizationSelection) {
throw new Error('setActive requires an organization parameter when organization selection is forced.');
Copy link
Member Author

@LauraBeatris LauraBeatris Jun 11, 2025

Choose a reason for hiding this comment

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

I believe it's better to not throw an error and just handle it idempotently as we're doing on the backend, cause it might break the entire application.

}

const onBeforeSetActive: SetActiveHook =
typeof window !== 'undefined' && typeof window.__unstable__onBeforeSetActive === 'function'
? window.__unstable__onBeforeSetActive
Expand Down Expand Up @@ -1107,7 +1103,16 @@ export class Clerk implements ClerkInterface {
const matchingOrganization = newSession.user.organizationMemberships.find(
mem => mem.organization.slug === organizationIdOrSlug,
);
newSession.lastActiveOrganizationId = matchingOrganization?.organization.id || null;

const newLastActiveOrganizationId = matchingOrganization?.organization.id || null;
const isPersonalWorkspace = newLastActiveOrganizationId === null;

// Do not update in-memory to personal workspace if force organization selection is enabled
if (this.environment?.organizationSettings?.forceOrganizationSelection && isPersonalWorkspace) {
return;
}

newSession.lastActiveOrganizationId = newLastActiveOrganizationId;
}
}

Expand Down
Loading