Skip to content

Commit 0776f5d

Browse files
committed
Add service provider step
1 parent ab905ac commit 0776f5d

4 files changed

Lines changed: 204 additions & 9 deletions

File tree

.changeset/public-eggs-brush.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/localizations': patch
3+
'@clerk/shared': patch
4+
'@clerk/ui': patch
5+
---
6+
7+
Add support for Microsoft Entra SAML provider to self-serve SSO

packages/localizations/src/en-US.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,26 @@ export const enUS: LocalizationResource = {
649649
step6: 'Select <bold>Assign</bold> at the bottom of the page.',
650650
},
651651
},
652+
serviceProviderStep: {
653+
headerSubtitle: 'Add service provider configuration to Microsoft Entra',
654+
title: 'Configure service provider',
655+
step1: 'In the side navigation, open the <bold>Manage</bold> dropdown and select Single sign-on.',
656+
step2:
657+
"In the <bold>Select a single sign-on</bold> method section, select <bold>SAML</bold>. You'll be redirected to the <bold>Set up Single Sign-On with SAML</bold> page.",
658+
step3: 'Find the <bold>Basic SAML Configuration</bold> section.',
659+
step4: 'Select <bold>Edit</bold>. The <bold>Basic SAML Configuration</bold> panel will open.',
660+
step5:
661+
'Add the following <bold>Identifier (Entity ID)</bold> and <bold>Reply URL (Assertion Consumer Service URL)</bold> values. These values will be saved automatically.',
662+
step6: 'Select <bold>Save</bold> at the top of the panel. Close the panel.',
663+
serviceProviderFields: {
664+
acsUrl: {
665+
label: 'Reply URL (Assertion Consumer Service URL)',
666+
},
667+
spEntityId: {
668+
label: 'Identifier (Entity ID)',
669+
},
670+
},
671+
},
652672
},
653673
},
654674
},

packages/shared/src/types/localization.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,6 +1694,24 @@ export type __internal_LocalizationResource = {
16941694
step6: LocalizationValue;
16951695
};
16961696
};
1697+
serviceProviderStep: {
1698+
headerSubtitle: LocalizationValue;
1699+
title: LocalizationValue;
1700+
step1: LocalizationValue;
1701+
step2: LocalizationValue;
1702+
step3: LocalizationValue;
1703+
step4: LocalizationValue;
1704+
step5: LocalizationValue;
1705+
step6: LocalizationValue;
1706+
serviceProviderFields: {
1707+
spEntityId: {
1708+
label: LocalizationValue;
1709+
};
1710+
acsUrl: {
1711+
label: LocalizationValue;
1712+
};
1713+
};
1714+
};
16971715
};
16981716
};
16991717
confirmation: {

packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/saml/SamlMicrosoftConfigureSteps.tsx

Lines changed: 159 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,39 @@
11
import { type JSX } from 'react';
22

33
import { Box, Col, descriptors, Heading, localizationKeys, Text } from '@/customizables';
4+
import { ClipboardInput } from '@/elements/ClipboardInput';
5+
import { Form } from '@/elements/Form';
6+
import { Checkmark, Clipboard } from '@/icons';
7+
import { useFormControl } from '@/ui/utils/useFormControl';
48

9+
import { useConfigureSSO } from '../../../ConfigureSSOContext';
510
import { Step } from '../../../elements/Step';
611
import { useWizard, Wizard } from '../../../elements/Wizard';
712
import { InnerStepCounter } from '../../../elements/Wizard/InnerStepCounter';
813

914
export const SamlMicrosoftConfigureSteps = (): JSX.Element => {
1015
return (
11-
<Wizard.Step id='create-app'>
12-
<Step.Header
13-
title={localizationKeys('configureSSO.configureStep.samlMicrosoft.mainHeaderTitle')}
14-
description={localizationKeys('configureSSO.configureStep.samlMicrosoft.createAppStep.headerSubtitle')}
15-
>
16-
<InnerStepCounter />
17-
</Step.Header>
18-
<SamlMicrosoftCreateAppStep />
19-
</Wizard.Step>
16+
<>
17+
<Wizard.Step id='create-app'>
18+
<Step.Header
19+
title={localizationKeys('configureSSO.configureStep.samlMicrosoft.mainHeaderTitle')}
20+
description={localizationKeys('configureSSO.configureStep.samlMicrosoft.createAppStep.headerSubtitle')}
21+
>
22+
<InnerStepCounter />
23+
</Step.Header>
24+
<SamlMicrosoftCreateAppStep />
25+
</Wizard.Step>
26+
27+
<Wizard.Step id='service-provider'>
28+
<Step.Header
29+
title={localizationKeys('configureSSO.configureStep.samlMicrosoft.mainHeaderTitle')}
30+
description={localizationKeys('configureSSO.configureStep.samlMicrosoft.serviceProviderStep.headerSubtitle')}
31+
>
32+
<InnerStepCounter />
33+
</Step.Header>
34+
<SamlMicrosoftServiceProviderStep />
35+
</Wizard.Step>
36+
</>
2037
);
2138
};
2239

@@ -220,3 +237,136 @@ const SamlMicrosoftCreateAppStep = (): JSX.Element => {
220237
</>
221238
);
222239
};
240+
241+
const SamlMicrosoftServiceProviderStep = (): JSX.Element => {
242+
const { goNext, goPrev, isFirstStep, isLastStep } = useWizard();
243+
const { enterpriseConnection } = useConfigureSSO();
244+
245+
const acsUrl = enterpriseConnection?.samlConnection?.acsUrl ?? '';
246+
const spEntityId = enterpriseConnection?.samlConnection?.spEntityId ?? '';
247+
248+
const spEntityIdField = useFormControl('spEntityId', spEntityId, {
249+
type: 'text',
250+
label: localizationKeys(
251+
'configureSSO.configureStep.samlMicrosoft.serviceProviderStep.serviceProviderFields.spEntityId.label',
252+
),
253+
isRequired: false,
254+
});
255+
const acsUrlField = useFormControl('acsUrl', acsUrl, {
256+
type: 'text',
257+
label: localizationKeys(
258+
'configureSSO.configureStep.samlMicrosoft.serviceProviderStep.serviceProviderFields.acsUrl.label',
259+
),
260+
isRequired: false,
261+
});
262+
263+
return (
264+
<>
265+
<Step.Body>
266+
<Step.Section sx={theme => ({ gap: theme.space.$5 })}>
267+
<Col sx={theme => ({ gap: theme.space.$1x5 })}>
268+
<Heading
269+
elementDescriptor={descriptors.configureSSOInstructionsHeading}
270+
as='h3'
271+
textVariant='subtitle'
272+
localizationKey={localizationKeys('configureSSO.configureStep.samlMicrosoft.serviceProviderStep.title')}
273+
/>
274+
275+
<Col
276+
elementDescriptor={descriptors.configureSSOInstructionsList}
277+
as='ul'
278+
sx={theme => ({
279+
gap: theme.space.$1x5,
280+
margin: 0,
281+
paddingInlineStart: theme.space.$5,
282+
listStyleType: 'disc',
283+
})}
284+
>
285+
<Text
286+
elementDescriptor={descriptors.configureSSOInstructionsListItem}
287+
as='li'
288+
colorScheme='secondary'
289+
localizationKey={localizationKeys('configureSSO.configureStep.samlMicrosoft.serviceProviderStep.step1')}
290+
/>
291+
<Text
292+
elementDescriptor={descriptors.configureSSOInstructionsListItem}
293+
as='li'
294+
colorScheme='secondary'
295+
localizationKey={localizationKeys('configureSSO.configureStep.samlMicrosoft.serviceProviderStep.step2')}
296+
/>
297+
<Text
298+
elementDescriptor={descriptors.configureSSOInstructionsListItem}
299+
as='li'
300+
colorScheme='secondary'
301+
localizationKey={localizationKeys('configureSSO.configureStep.samlMicrosoft.serviceProviderStep.step3')}
302+
/>
303+
<Text
304+
elementDescriptor={descriptors.configureSSOInstructionsListItem}
305+
as='li'
306+
colorScheme='secondary'
307+
localizationKey={localizationKeys('configureSSO.configureStep.samlMicrosoft.serviceProviderStep.step4')}
308+
/>
309+
<Text
310+
elementDescriptor={descriptors.configureSSOInstructionsListItem}
311+
as='li'
312+
colorScheme='secondary'
313+
localizationKey={localizationKeys('configureSSO.configureStep.samlMicrosoft.serviceProviderStep.step5')}
314+
/>
315+
</Col>
316+
</Col>
317+
318+
<Form.ControlRow elementId={spEntityIdField.id}>
319+
<Form.CommonInputWrapper {...spEntityIdField.props}>
320+
<ClipboardInput
321+
value={spEntityId}
322+
readOnly
323+
copyIcon={Clipboard}
324+
copiedIcon={Checkmark}
325+
/>
326+
</Form.CommonInputWrapper>
327+
</Form.ControlRow>
328+
329+
<Form.ControlRow elementId={acsUrlField.id}>
330+
<Form.CommonInputWrapper {...acsUrlField.props}>
331+
<ClipboardInput
332+
value={acsUrl}
333+
readOnly
334+
copyIcon={Clipboard}
335+
copiedIcon={Checkmark}
336+
/>
337+
</Form.CommonInputWrapper>
338+
</Form.ControlRow>
339+
340+
<Col
341+
elementDescriptor={descriptors.configureSSOInstructionsList}
342+
as='ul'
343+
sx={theme => ({
344+
gap: theme.space.$1x5,
345+
margin: 0,
346+
paddingInlineStart: theme.space.$5,
347+
listStyleType: 'disc',
348+
})}
349+
>
350+
<Text
351+
elementDescriptor={descriptors.configureSSOInstructionsListItem}
352+
as='li'
353+
colorScheme='secondary'
354+
localizationKey={localizationKeys('configureSSO.configureStep.samlMicrosoft.serviceProviderStep.step6')}
355+
/>
356+
</Col>
357+
</Step.Section>
358+
</Step.Body>
359+
360+
<Step.Footer>
361+
<Step.Footer.Previous
362+
onClick={() => goPrev()}
363+
isDisabled={isFirstStep}
364+
/>
365+
<Step.Footer.Continue
366+
onClick={() => goNext()}
367+
isDisabled={isLastStep}
368+
/>
369+
</Step.Footer>
370+
</>
371+
);
372+
};

0 commit comments

Comments
 (0)