Skip to content
7 changes: 7 additions & 0 deletions .changeset/beige-breads-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@clerk/localizations': patch
'@clerk/shared': patch
'@clerk/ui': patch
---

Add support for Google Workspace SAML provider to self-serve SSO
105 changes: 102 additions & 3 deletions packages/localizations/src/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ export const enUS: LocalizationResource = {
groupLabel: 'SAML',
okta: 'Okta Workforce',
customSaml: 'Custom SAML Provider',
google: 'Google Workspace',
},
warning: 'Once a provider is selected you cannot change again until the configuration is over',
},
Expand Down Expand Up @@ -359,11 +360,11 @@ export const enUS: LocalizationResource = {
step5: 'Click <bold>Next</bold> to complete creating the application.',
},
serviceProviderInstructions: {
title: 'Configure service provider',
title: 'Add service provider configuration to Okta',
paragraph1:
'Once you have moved forward from the General Settings instructions, you will be presented with the Configure SAML page.',
paragraph2:
'To configure your service provider (Clerk), you must add these two fields to your Okta application:',
'To configure your service provider, you must add these two fields to your Okta SAML application:',
serviceProviderFields: {
acsUrl: {
label: 'Single sign-on URL',
Expand All @@ -387,7 +388,7 @@ export const enUS: LocalizationResource = {
step2: 'Select <bold>Add Expression</bold> for each row below, then enter the matching name and value:',
attributeMappingTable: {
columns: {
name: 'Name',
name: 'Attribute name',
expression: 'Expression',
},
rows: {
Expand Down Expand Up @@ -516,6 +517,104 @@ export const enUS: LocalizationResource = {
},
},
},
samlGoogle: {
mainHeaderTitle: 'Configure Google Workspace',
createAppStep: {
headerSubtitle: 'Create a new enterprise application in your Google Workspace',
createAppInstructions: {
title: 'Create a new enterprise application in Google Workspace',
step1: 'Sign in to Google Admin Portal',
step2: 'In the side navigation, under <bold>Apps</bold>, select <bold>Web and mobile apps</bold>',
step3: 'Click on the <bold>Add</bold> app button, and select <bold>Add custom SAML app</bold>',
step4: 'In the <bold>App details</bold> section, fill out the required <bold>App name</bold>.',
step5: 'Select the <bold>Continue</bold> button',
},
},
identityProviderMetadataStep: {
headerSubtitle: 'Configure identity provider metadata',
modes: {
title: 'Fill in your Google Workspace application details',
ariaLabel: 'Configuration ',
metadataFile: 'Add via metadata',
manual: 'Configure manually',
},
metadataFile: {
label: 'IdP metadata',
description: 'In your Google Workspace app, download the IdP metadata and upload it below.',
uploadFile: 'Upload file',
replaceFile: 'Replace file',
removeFile: 'Remove file',
fileUploaded: 'File uploaded',
},
manual: {
description: 'In your Google Workspace app, retrieve these values.',
signOnUrl: {
label: 'SSO URL',
placeholder: 'Paste URL here...',
},
issuer: {
label: 'Entity ID',
placeholder: 'Paste URL here...',
},
signingCertificate: {
label: 'Signing certificate',
uploadFile: 'Upload file',
replaceFile: 'Replace file',
removeFile: 'Remove file',
fileUploaded: 'File uploaded',
},
},
},
serviceProviderStep: {
headerSubtitle: 'Configure service provider',
title: 'Configure service provider',
paragraph:
'To configure your service provider, you must add these two fields to your Google Workspace SAML application:',
serviceProviderFields: {
acsUrl: {
label: 'ACS URL',
},
spEntityId: {
label: 'Entity ID',
},
},
nameIdInstructions: {
step1:
'Under the <bold>Name ID</bold> section, select the <bold>Name ID</bold> format dropdown and select <bold>Email</bold>.',
step2: 'Select <bold>Continue</bold>',
},
},
attributeMappingStep: {
headerSubtitle: 'Map user attributes from Google Workspace to your application',
paragraph: 'We expect your SAML response to return the user’s email, first name and last name.',
step1: 'In the <bold>Google Admin Console</bold>, find the <bold>Attributes</bold> section.',
step2:
'Select <bold>Add mapping</bold> for each attribute, and enter the following Google and app attribute:',
attributeMappingTable: {
columns: {
googleAttribute: 'Google attribute',
appAttribute: 'App attribute',
},
rows: {
email: { googleAttribute: 'Primary email', appAttribute: 'email' },
firstName: { googleAttribute: 'First name', appAttribute: 'firstName' },
lastName: { googleAttribute: 'Last name', appAttribute: 'lastName' },
},
},
},
configureUserAccess: {
headerSubtitle: 'Enable your Google Workspace SAML workspace',
assignUsersInstructions: {
paragraph1:
"Once the configuration is complete in Google, you'll be redirected to the app's overview page.",
step1: 'Open the <bold>User access</bold> section.',
step2: 'Select <bold>ON</bold> for everyone.',
step3: 'Select <bold>Save</bold>.',
paragraph2:
'Google may take up to 24 hours to propagate these changes. The connection will remain inactive until they take effect.',
},
},
},
},
},
createOrganization: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ function useEnterpriseConnectionTestRuns(
},
enabled: queryEnabled,
refetchIntervalInBackground: false,
refetchOnWindowFocus: false,
});

const hasRows = (query.data?.data?.length ?? 0) > 0;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/types/elementIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type FieldId =
| 'apiKeySecret'
| 'idpCertificate'
| 'idpEntityId'
| 'idpMetadata'
| 'idpMetadataUrl'
| 'idpSsoUrl'
| 'acsUrl'
Expand Down
94 changes: 94 additions & 0 deletions packages/shared/src/types/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,7 @@ export type __internal_LocalizationResource = {
groupLabel: LocalizationValue;
okta: LocalizationValue;
customSaml: LocalizationValue;
google: LocalizationValue;
};
warning: LocalizationValue;
};
Expand Down Expand Up @@ -1570,6 +1571,99 @@ export type __internal_LocalizationResource = {
};
};
};
samlGoogle: {
mainHeaderTitle: LocalizationValue;
createAppStep: {
headerSubtitle: LocalizationValue;
createAppInstructions: {
title: LocalizationValue;
step1: LocalizationValue;
step2: LocalizationValue;
step3: LocalizationValue;
step4: LocalizationValue;
step5: LocalizationValue;
};
};
identityProviderMetadataStep: {
headerSubtitle: LocalizationValue;
modes: {
title: LocalizationValue;
ariaLabel: LocalizationValue;
metadataFile: LocalizationValue;
manual: LocalizationValue;
};
metadataFile: {
label: LocalizationValue;
description: LocalizationValue;
uploadFile: LocalizationValue;
replaceFile: LocalizationValue;
removeFile: LocalizationValue;
fileUploaded: LocalizationValue;
};
manual: {
description: LocalizationValue;
signOnUrl: {
label: LocalizationValue;
placeholder: LocalizationValue;
};
issuer: {
label: LocalizationValue;
placeholder: LocalizationValue;
};
signingCertificate: {
label: LocalizationValue;
uploadFile: LocalizationValue;
replaceFile: LocalizationValue;
removeFile: LocalizationValue;
fileUploaded: LocalizationValue;
};
};
};
serviceProviderStep: {
headerSubtitle: LocalizationValue;
title: LocalizationValue;
paragraph: LocalizationValue;
serviceProviderFields: {
acsUrl: {
label: LocalizationValue;
};
spEntityId: {
label: LocalizationValue;
};
};
nameIdInstructions: {
step1: LocalizationValue;
step2: LocalizationValue;
};
};
attributeMappingStep: {
headerSubtitle: LocalizationValue;
paragraph: LocalizationValue;
step1: LocalizationValue;
step2: LocalizationValue;
attributeMappingTable: {
columns: {
googleAttribute: LocalizationValue;
appAttribute: LocalizationValue;
};
rows: {
email: { googleAttribute: LocalizationValue; appAttribute: LocalizationValue };
firstName: { googleAttribute: LocalizationValue; appAttribute: LocalizationValue };
lastName: { googleAttribute: LocalizationValue; appAttribute: LocalizationValue };
};
};
};
configureUserAccess: {
headerSubtitle: LocalizationValue;
assignUsersInstructions: {
paragraph1: LocalizationValue;
step1: LocalizationValue;
step2: LocalizationValue;
step3: LocalizationValue;
paragraph2: LocalizationValue;
};
};
};
};
confirmation: {
statusSection: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

import { Box, descriptors, Flex, Icon, SimpleButton, Text } from '@/customizables';
import { ChevronRight, Checkmark } from '@/icons';
import { Checkmark, ChevronRight } from '@/icons';

import type { StepperItemProps, StepperProps } from './types';

Expand Down Expand Up @@ -88,6 +88,7 @@ const Item = ({
fontSize: theme.fontSizes.$xs,
fontWeight: theme.fontWeights.$medium,
color: theme.colors.$colorBackground,
lineHeight: '1rem',
})}
>
{bullet}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { useConfigureSSO } from '../../ConfigureSSOContext';
import { Step } from '../../elements/Step';
import { Wizard } from '../../elements/Wizard';
import type { ProviderType } from '../../types';
import { SamlCustomConfigureSteps } from './saml/SamlCustomConfigureSteps';
import { SamlOktaConfigureSteps } from './saml/SamlOktaConfigureSteps';
import { SamlCustomConfigureSteps, SamlGoogleConfigureSteps, SamlOktaConfigureSteps } from './saml';

const STEPS_BY_PROVIDER: Record<ProviderType, () => JSX.Element> = {
saml_custom: SamlCustomConfigureSteps,
saml_okta: SamlOktaConfigureSteps,
saml_google: SamlGoogleConfigureSteps,
};

export const ConfigureStep = (): JSX.Element | null => {
Expand Down
Loading
Loading