Skip to content

feat(ui): SAML metadata URL submission in ConfigureSSO Configure step#8535

Open
iagodahlem wants to merge 13 commits into
iago/orgs-1462from
iago/orgs-1456-configuresso-configure-step
Open

feat(ui): SAML metadata URL submission in ConfigureSSO Configure step#8535
iagodahlem wants to merge 13 commits into
iago/orgs-1462from
iago/orgs-1456-configuresso-configure-step

Conversation

@iagodahlem
Copy link
Copy Markdown
Member

Description

Adds the Okta SAML metadata URL submission path to the Configure step of <__experimental_ConfigureSSO />.

The user pastes their IdP metadata URL and the wizard advances on a successful PATCH /me/enterprise_connections/{id} with { saml: { idpMetadataUrl } }. The mutation is wrapped in useReverification, matching the established convention for sensitive user.* mutations in @clerk/ui. useCardState drives the loading state; handleError routes backend errors inline under the field when the API returns idp_metadata_url, or to the card-level error surface otherwise.

Locale keys added under configureSSO.configureStep in en-US.

Linear: ORGS-1456

Follow-ups (separate PRs)

  • Segmented control for manual entry + file upload modes
  • Read-only SP-side copy rows (ACS URL, Entity ID)
  • Okta admin-console instructional walkthrough
  • Attribute mapping / claim names guidance
  • Tests

How to test

Set up a sandbox where the ConfigureSSO wizard lands on the Configure step with an enterprise connection in context. Paste a valid Okta SAML metadata URL → Continue → connection is patched and the wizard advances. Backend errors keyed idp_metadata_url render inline; others render at the card level.

Base

Stacked on #8503. GitHub will auto-retarget this PR to main once that merges.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Adds the Okta SAML metadata URL path to the Configure step. The user
pastes their IdP metadata URL and the wizard advances on a successful
PATCH to user.updateEnterpriseConnection with { saml: { idpMetadataUrl } }.

The mutation is wrapped in useReverification, matching the established
convention for sensitive user.* mutations in @clerk/ui. useCardState
drives the loading state; handleError routes backend errors inline under
the field when the API returns idp_metadata_url, or to the card-level
error surface otherwise. Locale keys added under configureSSO.configureStep
in en-US.

Manual entry, file upload, SP-side copy rows, and the Okta admin-console
walkthrough are deferred to follow-up PRs.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 12, 2026

🦋 Changeset detected

Latest commit: 8584741

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
Name Type
@clerk/localizations Patch
@clerk/shared Patch
@clerk/ui Patch
@clerk/clerk-js Patch
@clerk/react Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/expo-passkeys Patch
@clerk/expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/hono Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/vue Patch

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

@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment May 13, 2026 2:17am

Request Review

Unblocks type-check for the SAML metadata URL input added to the
ConfigureSSO Configure step.
…ider

Mirrors the existing createConnection pattern: ConfigureSSOCardContent
destructures updateEnterpriseConnection from
__internal_useUserEnterpriseConnections and passes it as a prop to
ConfigureSSOProvider, which wraps it once in useReverification and exposes
it as updateConnection on the context.

The id is taken implicitly from enterpriseConnection in context, so call
sites don't thread it through. ConfigureStep now just calls
updateConnection({ saml: { idpMetadataUrl } }) and gets both
reverification and query revalidation for free, since the hook owns the
revalidate call after a successful mutation.
Drops the infoText option on useFormControl (which renders the helper
copy as a focus-triggered tooltip) and places the description as a
static <Text> element above the input. Styling mirrors the muted-body
treatment used in SelectProviderStep so the inline copy reads the same
as the tooltip did.

Also tightens the placeholder copy from the dummy metadata URL to a
neutral 'Paste URL here...'.
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant