diff --git a/.changeset/quiet-moons-shine.md b/.changeset/quiet-moons-shine.md
new file mode 100644
index 00000000000..b229db264f0
--- /dev/null
+++ b/.changeset/quiet-moons-shine.md
@@ -0,0 +1,7 @@
+---
+'@clerk/clerk-js': patch
+'@clerk/shared': patch
+'@clerk/ui': patch
+---
+
+Enable self-serve OIDC configuration for every application. Organization admins can now select an OIDC provider in the `` Security tab without the `experimental.oidcSelfServe` option, and existing OIDC connections open their configuration steps instead of the unsupported-provider state. The `experimental.oidcSelfServe` option no longer does anything and can be removed from `` and `Clerk.load()`.
diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts
index 77ce5435aae..cb3999bcd70 100644
--- a/packages/shared/src/types/clerk.ts
+++ b/packages/shared/src/types/clerk.ts
@@ -1513,10 +1513,6 @@ export type ClerkOptions = ClerkOptionsNavigation &
* directly with the provided Clerk instance. Used by React Native / Expo.
*/
runtimeEnvironment: 'headless';
- /**
- * Temporary flag that gates the self-serve OIDC flow in ``. Remove once the self-serve OIDC flow reaches GA.
- */
- oidcSelfServe: boolean;
},
Record
>;
diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx
index 156fc15eeed..cb6f963ea62 100644
--- a/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx
+++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSOContext.tsx
@@ -1,8 +1,6 @@
import type { EnterpriseConnectionResource, OrganizationDomainResource } from '@clerk/shared/types';
import React, { type PropsWithChildren } from 'react';
-import { useOptions } from '@/contexts';
-
import type { OrganizationEnterpriseConnection } from './domain/organizationEnterpriseConnection';
import type {
EnterpriseConnectionMutations,
@@ -28,8 +26,6 @@ export interface ConfigureSSOData {
testRuns: TestRunsView;
organizationDomains: OrganizationDomainResource[] | undefined;
onExit?: () => void;
- /** Temporary gate for the self-serve OIDC flow; remove at OIDC GA. */
- isOIDCFlowEnabled: boolean;
}
interface ConfigureSSOProviderProps {
@@ -57,8 +53,6 @@ export const ConfigureSSOProvider = ({
onExit,
children,
}: PropsWithChildren): JSX.Element => {
- const isOIDCFlowEnabled = useOptions().experimental?.oidcSelfServe ?? false;
-
const value = React.useMemo(
() => ({
contentRef,
@@ -69,7 +63,6 @@ export const ConfigureSSOProvider = ({
enterpriseConnectionMutations,
organizationDomainMutations,
onExit,
- isOIDCFlowEnabled,
}),
[
contentRef,
@@ -80,7 +73,6 @@ export const ConfigureSSOProvider = ({
organizationDomains,
enterpriseConnection,
onExit,
- isOIDCFlowEnabled,
],
);
diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/__tests__/ConfigureStep.test.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/__tests__/ConfigureStep.test.tsx
index f4511cc458e..60d4af2e957 100644
--- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/__tests__/ConfigureStep.test.tsx
+++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/__tests__/ConfigureStep.test.tsx
@@ -14,7 +14,6 @@ import type { EnterpriseConnectionProviderType } from '../../../types';
// left undefined so that footer self-hides in this isolated render.
const contextState = vi.hoisted(() => ({
provider: undefined as string | undefined,
- isOIDCFlowEnabled: true,
enterpriseConnection: undefined as
| {
id: string;
@@ -45,7 +44,6 @@ vi.mock('../../../ConfigureSSOContext', () => ({
provider: contextState.provider,
hasConnection: true,
},
- isOIDCFlowEnabled: contextState.isOIDCFlowEnabled,
}),
}));
@@ -63,34 +61,28 @@ const { createFixtures } = bindCreateFixtures('ConfigureSSO');
describe('resolveConfigureSteps', () => {
it('dispatches custom and legacy OIDC provider keys to the OIDC sub-flow', () => {
- expect(resolveConfigureSteps('oauth_custom_clerk_dev', true)).toBe(OidcCustomConfigureSteps);
- expect(resolveConfigureSteps('oidc_clerk_dev', true)).toBe(OidcCustomConfigureSteps);
- expect(resolveConfigureSteps('oidc_ghe_acme', true)).toBe(OidcCustomConfigureSteps);
- expect(resolveConfigureSteps('oidc_gitlab_ent_acme', true)).toBe(OidcCustomConfigureSteps);
- expect(resolveConfigureSteps('oidc_custom', true)).toBe(OidcCustomConfigureSteps);
- });
-
- it('does not dispatch OIDC providers while the experimental flow is disabled', () => {
- expect(resolveConfigureSteps('oauth_custom_clerk_dev', false)).toBeUndefined();
- expect(resolveConfigureSteps('oidc_clerk_dev', false)).toBeUndefined();
+ expect(resolveConfigureSteps('oauth_custom_clerk_dev')).toBe(OidcCustomConfigureSteps);
+ expect(resolveConfigureSteps('oidc_clerk_dev')).toBe(OidcCustomConfigureSteps);
+ expect(resolveConfigureSteps('oidc_ghe_acme')).toBe(OidcCustomConfigureSteps);
+ expect(resolveConfigureSteps('oidc_gitlab_ent_acme')).toBe(OidcCustomConfigureSteps);
+ expect(resolveConfigureSteps('oidc_custom')).toBe(OidcCustomConfigureSteps);
});
it('dispatches SAML providers by exact literal', () => {
- expect(resolveConfigureSteps('saml_okta', false)).toBe(SamlOktaConfigureSteps);
- expect(resolveConfigureSteps('saml_custom', false)).toBe(SamlCustomConfigureSteps);
- expect(resolveConfigureSteps('saml_google', false)).toBe(SamlGoogleConfigureSteps);
- expect(resolveConfigureSteps('saml_microsoft', false)).toBe(SamlMicrosoftConfigureSteps);
+ expect(resolveConfigureSteps('saml_okta')).toBe(SamlOktaConfigureSteps);
+ expect(resolveConfigureSteps('saml_custom')).toBe(SamlCustomConfigureSteps);
+ expect(resolveConfigureSteps('saml_google')).toBe(SamlGoogleConfigureSteps);
+ expect(resolveConfigureSteps('saml_microsoft')).toBe(SamlMicrosoftConfigureSteps);
});
it('returns undefined for an unrecognized provider so the caller can degrade', () => {
- expect(resolveConfigureSteps('ldap_enterprise' as EnterpriseConnectionProviderType, true)).toBeUndefined();
+ expect(resolveConfigureSteps('ldap_enterprise' as EnterpriseConnectionProviderType)).toBeUndefined();
});
});
describe('ConfigureProviderStep', () => {
beforeEach(() => {
contextState.provider = undefined;
- contextState.isOIDCFlowEnabled = true;
contextState.enterpriseConnection = undefined;
updateConnection.mockReset();
});
@@ -469,15 +461,4 @@ describe('ConfigureProviderStep', () => {
expect(await screen.findByText(/unsupported provider/i)).toBeInTheDocument();
});
-
- it('degrades to the unsupported-provider state for an existing OIDC connection when the flag is off', async () => {
- contextState.provider = 'oauth_custom_clerk_dev';
- contextState.isOIDCFlowEnabled = false;
- const { wrapper } = await createFixtures();
-
- renderStep(wrapper);
-
- expect(await screen.findByText(/unsupported provider/i)).toBeInTheDocument();
- expect(screen.queryByText(/create a new oidc application/i)).not.toBeInTheDocument();
- });
});
diff --git a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/index.tsx b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/index.tsx
index 6abf3e61005..c687f4c7d3b 100644
--- a/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/index.tsx
+++ b/packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/index.tsx
@@ -28,13 +28,8 @@ const STEPS_BY_SAML_PROVIDER: Record
export const resolveConfigureSteps = (
provider: EnterpriseConnectionProviderType,
- isOIDCFlowEnabled: boolean,
-): ConfigureStepsComponent | undefined => {
- if (isOidcProvider(provider)) {
- return isOIDCFlowEnabled ? OidcCustomConfigureSteps : undefined;
- }
- return STEPS_BY_SAML_PROVIDER[provider];
-};
+): ConfigureStepsComponent | undefined =>
+ isOidcProvider(provider) ? OidcCustomConfigureSteps : STEPS_BY_SAML_PROVIDER[provider];
export const ConfigureStep = (): JSX.Element => {
const { organizationEnterpriseConnection: c } = useConfigureSSO();
@@ -68,13 +63,13 @@ export const ConfigureStep = (): JSX.Element => {
};
export const ConfigureProviderStep = (): JSX.Element | null => {
- const { organizationEnterpriseConnection: c, isOIDCFlowEnabled } = useConfigureSSO();
+ const { organizationEnterpriseConnection: c } = useConfigureSSO();
if (!c.provider) {
return null;
}
- const ConfigureSteps = resolveConfigureSteps(c.provider, isOIDCFlowEnabled);
+ const ConfigureSteps = resolveConfigureSteps(c.provider);
return (
diff --git a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx
index 7fde28ee673..c939cd88784 100644
--- a/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx
+++ b/packages/ui/src/components/ConfigureSSO/steps/SelectProviderStep.tsx
@@ -78,16 +78,10 @@ export const SelectProviderStep = (): JSX.Element => {
organizationEnterpriseConnection: c,
enterpriseConnectionMutations: { createConnection, changeProvider },
contentRef,
- isOIDCFlowEnabled,
} = useConfigureSSO();
const { goNext, goPrev, isFirstStep } = useWizard();
const { t } = useLocalizations();
- const providerGroups = React.useMemo(
- () => PROVIDER_GROUPS.filter(group => group.id !== 'oidc' || isOIDCFlowEnabled),
- [isOIDCFlowEnabled],
- );
-
const currentCard = c.provider ? toProviderCard(c.provider) : null;
const [selected, setSelected] = React.useState(currentCard);
@@ -166,7 +160,7 @@ export const SelectProviderStep = (): JSX.Element => {
({ gap: theme.space.$5 })}>
- {providerGroups.map(group => (
+ {PROVIDER_GROUPS.map(group => (
({
provider: undefined as 'saml_okta' | 'saml_custom' | 'saml_google' | undefined,
hasConnection: false,
- isOIDCFlowEnabled: false,
}));
vi.mock('../../ConfigureSSOContext', () => ({
@@ -40,7 +39,6 @@ vi.mock('../../ConfigureSSOContext', () => ({
provider: contextState.provider,
hasConnection: contextState.hasConnection,
},
- isOIDCFlowEnabled: contextState.isOIDCFlowEnabled,
}),
}));
@@ -64,7 +62,6 @@ const resetMocks = () => {
changeProvider.mockResolvedValue(undefined);
contextState.provider = undefined;
contextState.hasConnection = false;
- contextState.isOIDCFlowEnabled = false;
};
describe('SelectProviderStep', () => {
@@ -96,7 +93,7 @@ describe('SelectProviderStep', () => {
// Each provider card is a