feat(ui): build out Okta Configure step UI in ConfigureSSO#8535
Conversation
🦋 Changeset detectedLatest commit: 1dc6f12 The changes in this PR will be included in the next version bump. This PR includes changesets to release 20 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| const ATTRIBUTE_PAIRS = [ | ||
| { | ||
| id: 'email', | ||
| name: localizationKeys('configureSSO.configureStep.samlOkta.configureAttributes.pairs.email.name'), |
There was a problem hiding this comment.
In follow-up PRs, are we going to refactor those to receive the IdP type as a param in order to return different localization keys?
There was a problem hiding this comment.
Yep, that's the idea. The provider is already available from the ConfigureSSOContext. For now, it will always render as if Okta was selected.
facf667 to
fecd7a4
Compare
9ac22ff to
847bbd2
Compare
fecd7a4 to
43b002f
Compare
Mirrors VerifyDomainStep's nested wizard pattern: the outer Step is now a pure shell wrapping an inner Wizard with four Wizard.Step children — create-app, configure-attributes, assign-users, submit-saml-config. Step.Header renders an InnerStepCounter so the body shows Step X/4 as the user moves through the sub-steps. The existing metadata URL form moves into SubmitSamlConfigSubStep unchanged — same useReverification, useCardState, handleError wiring, same field, same PATCH. The first three sub-steps are placeholders with Previous/Continue scaffolding; content lands in follow-up commits. goNext/goPrev bubble across the wizard boundary natively (the Wizard primitive supports nested parent navigation), so the form's Continue handler still advances to the outer Test step on a successful PATCH without any cross-boundary plumbing.
Adds flex:1 to SubmitSamlConfigSubStep's Step.Section (was missing, so the footer didn't sit flush with the card edge) and drops the align/justify props on the placeholder sub-steps. Pure layout polish.
The function previously ran deepCamelToSnake(params), producing a nested
body like { saml: { idp_metadata_url } }. The backend expects the SAML
and OIDC fields prefixed at the top level (saml_idp_metadata_url,
oidc_client_id, etc.), so IdP metadata submissions in
<__experimental_ConfigureSSO /> were silently rejected.
Replaces the helper with a manual flat-field mapper: top-level fields
stay top-level, SAML fields get a saml_ prefix, OIDC fields get an oidc_
prefix. attribute_mapping and custom_attributes pass through unchanged
since their inner keys are user-supplied and must not be transformed.
A small setIfDefined helper makes the omit-undefined / forward-null
semantics explicit, so users can clear a field by sending null without
the SDK silently dropping it.
Mirrors the fix Laura validated in the SAML POC PR.
Step.Body already fills the vertical space between header and footer,
but Step.Section as a flex item defaults to flex:0 — so sections inside
the body shrink to content height unless told to grow. Until now each
sub-step had to repeat sx={{ flex: 1 }} on its Step.Section.
Defaulting flex:1 on Step.Section doesn't work because Step.Header
reuses the same primitive internally and needs to stay content-height,
and a single sub-step may stack multiple Sections where only one should
fill.
Adds an opt-in fill boolean prop. <Step.Section fill> applies flex:1;
the default behavior stays unchanged. Updates the four Configure
sub-step bodies to use the new prop. Other consumers (VerifyDomain,
SelectProvider) keep the old sx={{ flex: 1 }} pattern and can adopt the
prop in follow-ups.
Replaces the placeholder body of the first inner sub-step with three
stacked content groups:
1. Create new Okta app — section heading + bulleted list of 5
Okta admin-console steps (Sign in to Okta, click Create App
Integration, select SAML 2.0, fill General Settings, click Next).
2. Configure service provider — section heading + 2 description
paragraphs + 2 read-only copy rows for the SP-side ACS URL and
Audience URI. Values pull from connection.samlConnection.acsUrl
and spEntityId in the provider context. Uses the existing
ClipboardInput primitive so each row gets a copy-to-clipboard
button.
3. Complete SAML integration — section heading + bulleted list of
2 follow-up Okta admin-console steps.
All three groups live in one Step.Section fill with a generous gap so
the body scrolls naturally if needed; Step.Header keeps the only
border-bottom separator.
Bold keywords inside instruction lines (Admin → Applications,
Create App Integration, SAML 2.0, etc.) are split into prefix / bold /
suffix localization keys per line. Clerk's localization helper only
supports {{token}} string interpolation, so this keeps the bold span
themable through the existing Text primitive while still letting
translators see each instruction line as discrete units.
Locale keys added under configureSSO.configureStep.createApp in en-US.
Layout tightening across the inner Configure wizard: - Move Step.Body inside each sub-step component so the wizard switches bodies cleanly between sub-steps instead of nesting Wizard.Step children under a single outer Step.Body. - Wrap the ACS URL and Audience URI copy rows in Form.ControlRow + Form.CommonInputWrapper + ClipboardInput so the rows reuse the standard form chrome (label rendering, error slot, spacing) and the ClipboardInput primitive's readOnly + copyIcon/copiedIcon API. Adds 'acsUrl' to the FieldId union to back the new useFormControl call sites. - Bring group headings down to textVariant='subtitle' so the body reads as supporting content under the existing Step.Header title. - Tighten vertical spacing: Step.Section gap drops from $6 to $5, inner-group heading-to-content gap from $3 to $1x5, list-item gap from $1 to $1x5. - Soften the bold span in instructional lines from $semibold + $colorForeground to $medium + $colorMutedForeground so the emphasis feels like keyword highlighting rather than full bold.
Replaces the placeholder body of the second inner sub-step with two stacked content groups: 1. SAML attribute mapping — section heading + a 3-row attribute table built from the Table primitives (Thead / Tbody / Tr / Th / Td) and rendered with monospace claim-name cells. Each row pairs the attribute label with a Badge: warning colorScheme for the required Email row, secondary colorScheme for the optional First/Last name rows. Claim names render in an inline code span using the same monospace + neutralAlpha100 background + small radius styling as the InstructionStepWithCode helper. 2. Verify the attribute mappings in Okta — description paragraph + a numbered ordered list of 9 Okta admin-console steps. Shape-A lines (1, 4, 7) use the existing InstructionStep helper (prefix / bold / suffix); Shape-B lines (2, 3, 5, 6, 8, 9) use the new InstructionStepWithCode helper (prefix / bold / middle / code / suffix) so the mail / firstName / lastName values render as inline code spans. Mirrors the layout conventions established in the sibling sub-step (Step.Body inside the sub-step, single Step.Section with gap $5, inner groups in Cols with gap $1x5, headings as textVariant subtitle, medium-weight muted bold span). Adds the matching locale type entries and English copy under configureSSO.configureStep.configureAttributes.
…table styling
Replaces sx={{ color: $colorMutedForeground }} with Text colorScheme='secondary'
across ConfigureAttributesSubStep — the prop already resolves to the same color
token, so the inline sx call drops out cleanly.
Tightens the attribute mapping table chrome: column headers shrink to
fontSize=$xs, the first column picks up an inline-start pad so the leading
cell breathes against the table edge, and the claim-name cells reduce to
fontFamily: monospace only (drops the background, border-radius, and
padding from the earlier 'code chip' treatment for a flatter look that
reads as data, not as inline code).
Inner Cols inside ConfigureAttributesSubStep step up from gap $1x5 to $3.
Numbered/bulleted list indents grow from paddingInlineStart $4 to $5.
…opt inline rich-text markup
Restructures localization keys under configureSSO.configureStep so future
SAML providers (Custom SAML) and OIDC can drop in alongside Okta without
duplicating shared copy:
- spFields and attributeMapping live at the top level since their labels,
table content, badges, and the "These are the defaults..." paragraph
read the same regardless of provider.
- samlOkta now owns provider-specific copy: title, subtitle, createApp
walkthrough, serviceProvider narrative, completeSamlIntegration steps,
configureAttributes pairs, metadataUrl. When Custom SAML lands, a
sibling samlCustom namespace mirrors this shape.
Replaces the InstructionStep and InstructionStepWithCode helpers (which
required 3 or 5 separate keys per sentence) with a single RichText
component that parses inline <strong>...</strong> and <code>...</code>
markup in a localized string. One key per sentence, translators see the
whole context, emphasis stays themable through Text spans.
ConfigureAttributesSubStep redesigned to match Figma 8032:14794:
- Claim names in the attribute mapping table are now user.email,
user.firstName, user.lastName (corrects user.profile.email).
- The verify-mappings list collapses from 9 separate numbered steps to 2,
with a nested bulleted sub-list of the name/expression pairs the user
enters in Okta.
- Table rows render from an ATTRIBUTE_ROWS constant so the row markup
isn't duplicated three times.
- Verify-mappings pairs render from an ATTRIBUTE_PAIRS constant for the
same reason.
Sweeps remaining sx={{ color: $colorMutedForeground }} call sites in the
Configure sub-steps over to Text colorScheme='secondary' to match the
pattern used elsewhere in the file.
…ns back to multi-key inline spans
Drops the RichText helper, parseRichText helper, and the
<strong>/<code> markup convention added in the previous commit.
Each instructional line in the Configure sub-steps now uses three
(or more) separate localization keys per sentence — prefix, bold,
suffix — rendered inline as Text-as-span children of a single
Text-as-li parent. Matches Laura's verifyEmailDomainStep precedent
and uses vanilla Clerk localization primitives end-to-end.
The attribute-mapping pairs in ConfigureAttributesSubStep render
as Badge components rather than inline monospace code spans. Each
pair is now name + conjunction + expression — three locale keys
per pair, with the conjunction localized so other languages can
pick their own.
Adds the third inner sub-step, AssignUsersSubStep, with the same
structure as the other sub-steps: section heading, description
paragraph, and a numbered ordered list of five Okta admin-console
steps walking the user through the Assignments tab flow. Step 2
has three emphasized keywords ("Assign", "Assign to people",
"Assign to groups") and uses a seven-key inline shape; the other
steps use the standard three-key shape, except step 3 which is
plain prose and renders as a single Text-as-li.
All new copy lives under configureSSO.configureStep.samlOkta so
Custom SAML and OIDC siblings can drop in without colliding.
Sets the Optional badge to colorScheme='primary' explicitly rather than falling through to Badge's default, so the styling is intentional. Swaps the pairs sub-list bullet from listStyleType: 'disc' to a literal '- ' marker so the dash sits closer to the badges and reads as a softer delimiter than the filled disc.
…n SubmitSamlConfigSubStep Mirrors the same pattern Iago adopted for SelectProviderStep: ConfigureSSOProvider no longer plumbs the enterprise-connection mutation through. SubmitSamlConfigSubStep now calls __internal_useUserEnterpriseConnections itself, grabs updateEnterpriseConnection, and wraps it in a local useReverification(useCallback(...)). Drops updateConnection from ConfigureSSOData, drops updateEnterpriseConnection from ConfigureSSOProviderProps, and stops threading the function through ConfigureSSO.tsx. The provider keeps ownership of provider-selection state (provider, setProvider, createConnection) since SelectProviderStep's create-flow still expects that shape until it gets the same treatment next.
…y per line Drops the prefix/bold/suffix (and the seven-key prefix/bold1/middle1/.../suffix on Assign Users step 2) shapes that we were threading through nested Text-as-span chains in the Configure sub-steps. Each Okta-walkthrough sentence now lives under one LocalizationValue, rendered as a single Text-as-li with localizationKey. A guide for safely embedding bold or other inline emphasis inside LocalizationValue strings is being prepared. Until that primitive lands the sub-steps read as plain prose — visually less hierarchy on the emphasized keywords, but a much smaller key surface and no fragile multi-span composition. The attribute-mapping pairs in Configure Attributes still use a pair of Badges plus a localized conjunction so each value stays themable as a distinct chip rather than collapsing into the surrounding sentence.
Switch Step header to h2 with secondary description text, apply unstyled scrollbar to the body, and tighten Col/Section spacing across CreateApp and ConfigureAttributes sub-steps.
3bb089a to
73c1213
Compare
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR implements an Okta SAML IdP metadata URL submission flow for the experimental SSO Configure step. It fixes the enterprise connection request body serializer to emit flat snake_case fields instead of nested objects, adds TypeScript type definitions and localization schemas for the new SAML configuration UI, provides en-US locale strings for service provider fields and Okta setup instructions, enhances the Step component styling for wizard layouts, and redesigns ConfigureStep as a multi-step wizard with sub-steps for app creation, attribute configuration, user assignment, and metadata submission. Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx`:
- Around line 129-133: The spEntityIdField is incorrectly created with the same
field ID 'acsUrl', causing duplicate form control IDs; update the useFormControl
call that constructs spEntityIdField to use the correct unique ID (e.g.,
'spEntityId') instead of 'acsUrl' so it no longer conflicts with acsUrlField;
ensure the call to useFormControl('spEntityId', spEntityId, {...}) (matching the
spEntityIdField variable and its label/localization key) is used so form state
and accessibility are correct.
- Around line 200-218: The two clipboard inputs are inconsistent: acsUrlField is
wrapped with Form.ControlRow but spEntityIdField is not; wrap the
spEntityIdField block in a Form.ControlRow like the acsUrlField to ensure
consistent layout and descriptor association. Specifically, locate the
ClipboardInput for spEntityId (using spEntityIdField.props and
value={spEntityId}) and wrap its existing Form.CommonInputWrapper inside a
Form.ControlRow with elementId={spEntityIdField.id}, mirroring the structure
used for acsUrlField (which uses Form.ControlRow -> Form.CommonInputWrapper ->
ClipboardInput).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 8e9c736a-d56c-4e11-8251-4a341dd9d6d0
📒 Files selected for processing (9)
.changeset/configure-sso-configure-step-metadata-url.md.changeset/fix-enterprise-connection-flat-body.mdpackages/clerk-js/src/core/resources/User.tspackages/clerk-js/src/core/resources/__tests__/User.test.tspackages/localizations/src/en-US.tspackages/shared/src/types/elementIds.tspackages/shared/src/types/localization.tspackages/ui/src/components/ConfigureSSO/elements/Step.tsxpackages/ui/src/components/ConfigureSSO/steps/ConfigureStep.tsx
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/dev-cli
@clerk/expo
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
… import path Three small fixes folded together: - Add 'spEntityId' to the FieldId union and switch the Audience URI field to use it. The field was previously sharing 'acsUrl' as its id, which collides with the Single Sign-On URL field above it. CodeRabbit flagged this on PR review. - Wrap the Audience URI clipboard input in Form.ControlRow so it matches the Single Sign-On URL row's chrome. The previous markup rendered the input without the row wrapper and was inconsistent. - Fix the useFormControl import from '@/utils/useFormControl' to '@/ui/utils/useFormControl' so it resolves through vitest's UI alias instead of the clerk-js catch-all. ConfigureSSO unit tests were failing on CI because Vite couldn't resolve the file.
… in rebase The Verify Domain step's English copy and matching LocalizationResource type entries were dropped from en-US.ts and shared/types/localization.ts during a rebase conflict resolution. Other locale files (es-ES, fr-FR, de-DE, pt-BR, etc.) still carried the keys, so the prod runtime worked for those locales but English consumers fell back to raw keys. Restores the verifyEmailDomainStep block at its original position under configureSSO in both files, matching what main has today.
The previous restore commit added a verifyEmailDomainStep block under configureSSO in __internal_LocalizationResource, but the same block was still present further down — vitest's typecheck flagged it as a duplicate identifier and the shared package's test job failed in CI. Keeps the original (higher) declaration intact and removes the redundant copy. The en-US.ts side wasn't duplicated, so this is a shared-types-only fix.
Description
Builds out the Configure step UI for
<__experimental_ConfigureSSO />(Okta provider). The Configure step is now a nested 4-sub-step inner wizard, mirroring the pattern Laura established for the Verify Domain step:connection.samlConnection.{acsUrl, spEntityId}.user.email/user.firstName/user.lastNameclaim names) + guidance on setting up Okta's Attribute Statements, with name/expression pairs rendered as badges.PATCH /me/enterprise_connections/{id}with{ saml: { idpMetadataUrl } }.The mutation is hoisted onto
ConfigureSSOProviderand wrapped inuseReverification, matching the convention for sensitiveuser.*mutations in@clerk/ui.useCardStatedrives the loading state;handleErrorroutes backend errors inline under the field when the API returnsidp_metadata_url, or to the card-level error surface otherwise. The hook owns query revalidation after a successful mutation, so the in-contextenterpriseConnectionreflects the new IdP metadata without an explicit refetch.Locale keys live under
configureSSO.configureStep. Shared keys (attribute mapping table, badges, SP field labels) sit at the top of the namespace; provider-specific copy lives underconfigureSSO.configureStep.samlOkta. Future Custom SAML and OIDC providers can drop in a sibling namespace without colliding.Also fixes
toMeEnterpriseConnectionBodyin@clerk/clerk-jsto emit a flat snake-case body (saml_idp_metadata_url) instead of the nested shape (saml: { idp_metadata_url }) the previous helper produced. The backend expects flat-prefixed fields, so the prior shape was silently rejected foruser.createEnterpriseConnectionanduser.updateEnterpriseConnection.Linear: ORGS-1456
Follow-ups (separate PR)
The "Configure manually" segmented-control mode of sub-step 4 ships in a follow-up PR. That adds:
FieldIdentries)Tests for the Configure sub-steps ship alongside Laura's #8520 follow-up sweep once both land.
How to test
In a sandbox with the ConfigureSSO wizard, enter through Select Provider → Verify Domain → land on Configure. Walk through sub-steps 1 through 3 (Previous / Continue navigate the inner wizard), then on sub-step 4 paste a valid Okta SAML metadata URL → Continue → connection is patched and the wizard advances to Test. Backend errors keyed
idp_metadata_urlrender inline; others surface at the card level.Base
Stacked on #8503. GitHub will auto-retarget this PR to `main` once that merges.
Checklist
Type of change