V1 Web PWA · Journey

Journey: Tenant SSO configuration — SAML / OIDC + claim mapping + auth policy

A source-reconciled journey record for V1 Web PWA — shipped behavior, state boundaries, failure modes, and the automation evidence available today.

automated · 5 specs verdict: partial· 2026-05-29
9sections9 minread1table

On this page
Journey at a glance
ActorsPlatform SSO operatorTenant adminOIDC identity providerSAML identity providerTenant member
Automation5 E2E spec(s) · verdict: partial· 2026-05-29

Journey flow#

Generated from the authored steps below — click a node to jump to that section.

flowchart TD s1["1. Tenant admin inspects the read-only identi…"] s2["2. Platform operator creates or imports a con…"] s3["3. Operator edits mappings, flows, and lifeti…"] s4["4. Operator runs the connection probe"] s5["5. Tenant admin reads the fixed auth-policy d…"] s6["6. Member completes the live OIDC login"] s7["7. Operator audits, updates, or removes the c…"] s1 --> s2 --> s3 --> s4 --> s5 --> s6 --> s7 click s1 href "#1-tenant-admin-inspects-the-read-only-identity-cockpit" click s2 href "#2-platform-operator-creates-or-imports-a-connection" click s3 href "#3-operator-edits-mappings-flows-and-lifetimes" click s4 href "#4-operator-runs-the-connection-probe" click s5 href "#5-tenant-admin-reads-the-fixed-auth-policy-demonstration" click s6 href "#6-member-completes-the-live-oidc-login" click s7 href "#7-operator-audits-updates-or-removes-the-connection" s7 --> v(["verdict: partial"]) click v href "../results/tenant-sso-config-saml-oidc-claim-mapping.html" style v stroke:#d6a531,color:#d6a531

This is a split-surface journey, not one tenant-admin wizard. A platform operator creates, imports, edits, probes, and deletes connections in /operator/sso. A tenant admin can only read the resulting tenant-scoped roster in /identity; the policy cards below that roster run fixed sample inputs. OIDC has a live SP-initiated login and callback. SAML can be configured and probed, but SAML login is explicitly deferred.

Capability Shipped boundary
SAML metadata Import, persist, edit, delete, and reachability probe
OIDC discovery Import, persist, edit, probe, and live SP-initiated login
Claim mapping Seven internal slots and four transforms, applied by OIDC callback
Tenant-admin identity page Tenant-filtered read-only roster plus sample policy evaluations
Auth policy Pure evaluator and fixed sample only; no tenant policy store or editor
Enablement No draft/probed/enabled state; a valid saved connection has no activation gate

Personas#

  • Platform SSO operator — holds admin:* or admin:workspace:tenant-console and owns the live /operator/sso editor.
  • Tenant admin — holds a tenant-admin session and inspects only the connections for that session's tenant at /identity.
  • OIDC identity provider — supplies discovery and JWKS documents, then posts a signed id_token to the callback.
  • SAML identity provider — supplies metadata and an SSO endpoint. It is not a live sign-in participant in V1.
  • Tenant member — starts an OIDC login after an operator has saved a runtime-complete connection.

Pre-conditions#

  • The operator is authenticated with one of the two SSO-admin scopes. The BFF does not authorize writes from a tenant-admin session.
  • The connection input has a valid tenant id, protocol, IdP entity/issuer, at least one HTTP(S) endpoint, positive session and refresh lifetimes, and a required email mapping.
  • A live OIDC connection also has oidcClientId, oidcJwksUrl, and an sso authorization endpoint.
  • The tenant-admin reader has a session whose payload contains the tenant id used by GET /v1/tenant-console/sso.
  • Deployable BFF boot requires the admin-database snapshot sink and hydrates it before traffic. Missing or failed primary persistence fails closed; a route cannot acknowledge a connection mutation that exists only in process memory.

Steps#

1. Tenant admin inspects the read-only identity cockpit#

  • /identity redirects an unauthenticated visitor before any tenant state renders.
  • The page reads GET /v1/tenant-console/sso with the session tenant id and renders connection id, protocol, IdP entity, allowed flows, JIT state, session minutes, and mapping count.
  • Empty and backend-unavailable states are explicit.
  • A second section runs SSO, SCIM, roster, auth-challenge, and federation engines against labeled sample inputs.
  • The page has no create, import, edit, test, delete, or policy-write control. Its sample JIT ready and step-up-required values are demonstrations, not the saved tenant's evaluated policy.

2. Platform operator creates or imports a connection#

The write surface is /operator/sso.

Manual create

  • POST /v1/admin/sso accepts an already-shaped SsoConnectionInput.
  • Store validation rejects an invalid protocol, missing endpoint, bad URL, non-positive lifetime, or a mapping set without a required email slot.

SAML metadata import

  • POST /v1/admin/sso/import extracts entity id, SSO/SLO endpoints, and certificate thumbprint from XML.
  • Missing entity id, SSO endpoint, or certificate returns structured validation issues and writes nothing.

OIDC discovery import

  • The import route fetches discovery and persists issuer, authorization endpoint, JWKS URL, and client id.
  • Closed-port, 404, 503, and missing-jwks_uri cases fail closed and leave the roster unchanged.

3. Operator edits mappings, flows, and lifetimes#

  • The mapping editor exposes exactly: email, displayName, familyName, givenName, groups, tenant-role, and preferred-locale.
  • Each row stores an external claim, required flag, and one transform: identity, lowercase, csv-split, or first-only.
  • The editor also persists IdP/SP flow flags, JIT, session lifetime, and refresh lifetime through PATCH /v1/admin/sso/:id.
  • Config writes use a serialized candidate/save/publish queue through wireDurableSsoConnections; create, import, update, and delete acknowledge only after the primary snapshot commits.
  • groups and tenant-role remain mapped attributes only. No route translates group strings into tenant role templates.
  • There is no draft/probed/enabled status, reviewer gate, or activation timestamp.

4. Operator runs the connection probe#

  • Test calls POST /v1/admin/sso/:id/test and renders a structured, fail-closed verdict.
  • OIDC checks discovery plus a JWKS containing at least one signing key.
  • SAML checks endpoint liveness and certificate-thumbprint shape.
  • A failed check returns HTTP 200 with ok: false so the UI can name the failed probe without fabricating reachability.
  • This is a connection probe, not a sandbox user login. It does not post an assertion, apply mappings, provision an account, or exercise MFA.

5. Tenant admin reads the fixed auth-policy demonstration#

  • evaluateAuthChallenge supports MFA, IPv4 allowlists, device posture, inactivity, and named step-up triggers.
  • The tenant-admin page's fixed sample intentionally returns step-up-required / device-posture.
  • No persisted TenantAuthPolicy is loaded for the tenant.
  • No UI or API writes MFA methods, triggers, CIDRs, posture requirements, refresh interval, or inactivity interval.
  • The CIDR helper is IPv4-only; IPv6 never matches an allowlist entry.

6. Member completes the live OIDC login#

  • GET /v1/auth/sso/:connectionId/login requires an OIDC connection with SP initiation enabled and runtime fields present.
  • It issues single-use state and nonce in a process-local map with a ten-minute TTL, then redirects to the IdP using response_type=id_token and response_mode=form_post.
  • POST /v1/auth/sso/callback consumes state before verification, fetches JWKS, verifies signature, issuer, audience, nonce, expiry, and algorithm, then applies the saved claim mappings.
  • The callback finds an existing user by mapped email or calls provisionFederatedUser when JIT is allowed, then returns a real customer session.
  • Missing required claims, disabled JIT, disabled SP flow, unknown state, JWKS failure, and signature failure never mint a session.
  • IdP-initiated login is modeled by the domain function but has no live route. SAML login returns 503 sso_protocol_not_supported.
  • The process-local state/nonce map is not shared across BFF replicas.

7. Operator audits, updates, or removes the connection#

  • Successful create/import/update/delete/probe calls append tenant.sso.connection.* or tenant.sso.probe.completed events with tenant, connection, protocol, and IdP context.
  • Duplicate deletion returns 404 and does not mint another delete event.
  • Tenant-admin read-back reflects later updates and deletion through the tenant-filtered read route.
  • Login success/failure does not join the admin SSO audit chain in this route module.

Post-conditions#

  • Every accepted create, import, update, and delete is committed to the primary snapshot before the response. A failed save returns 503 without publishing volatile state, and restart restores the canonical committed roster.
  • The operator roster and tenant-filtered read-only roster agree on the saved connection.
  • OIDC can mint a customer session through the live callback; SAML cannot.
  • The probe and login have distinct evidence: reachability does not imply a successful user assertion.
  • No statement in this journey treats sample auth-policy cards, claim mapping, or connection persistence as an enablement state or role-template binding.

Failure modes#

  • Wrong tenant visibility — the tenant-console read is filtered by session tenant; the operator admin endpoint can list all tenants or an explicit filter and therefore requires privileged scope.
  • Invalid import — structured issues are returned and no connection is created.
  • Persistence unavailable — mutation returns stable 503; the prior committed roster remains live and is the state restored after restart.
  • Probe failure — the connection remains saved; ok: false is evidence to repair configuration, not an automatic disable transition.
  • Required claim missing — callback returns a named mapping error before provisioning.
  • Unknown or expired state — callback returns 400; state is single-use even when later verification fails.
  • Flow toggled off after login starts — callback mapping fails closed with flow-not-permitted.
  • OIDC key mismatch — verification returns 401 and no session.
  • SAML member attempts login — V1 returns 503 because only the configuration and probe paths exist.
  • IPv6 allowlist input — the current evaluator returns ip-blocked when an IPv4-only CIDR cannot match it.
  • Replica switch during callback — process-local state may be absent on the receiving replica.

E2E coverage#

Coverage is partial: the two real operator/OIDC spines are deep, but the tenant-admin setup editor, tenant auth-policy persistence, activation state, role binding, IdP-initiated route, and SAML login do not exist.

Per-view files touched by this journey#

Cross-references#

  • tenant-bulk-member-invite-scim.md — separate invite and SCIM projections; neither enables this connection.
  • tenant-onboard.md — invite acceptance; it does not automatically start SSO.
  • tenant-audit-log-investigation.md — management-event inspection.
  • libs/oshun/tenant-console/src/identity/sso.ts — mapping and login domain contract.
  • libs/oshun/tenant-console/src/identity/federation-and-auth-policy.ts — pure policy evaluators.
  • apps/oshun/bff/src/tenant-console/sso-route.ts — privileged management and probe routes.
  • apps/oshun/bff/src/auth/sso-login-routes.ts — live OIDC-only runtime.

Open questions#

  • Which tenant-admin permission, if any, should be allowed to mutate its own connections without the platform operator's global route?
  • Should connections gain an explicit draft → probed → enabled → disabled state?
  • Where should a persisted per-tenant auth policy live, and which runtime requests must enforce it?
  • How do mapped groups and tenant-role values bind to approved tenant roles?
  • Should login state move to a shared, single-use store for multi-replica deployments?
  • What is the supported IdP-initiated URL contract?
  • Is SAML login in V1 scope, or should the editor label it configuration/probe only?
  • What is the explicit IPv6 allowlist policy?