Journey flow#
Generated from the authored steps below — click a node to jump to that section.
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:*oradmin:workspace:tenant-consoleand owns the live/operator/ssoeditor. - 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_tokento 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 anssoauthorization 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#
-
/identityredirects an unauthenticated visitor before any tenant state renders. - The page reads
GET /v1/tenant-console/ssowith 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 readyandstep-up-requiredvalues 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/ssoaccepts an already-shapedSsoConnectionInput. - 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/importextracts 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_uricases 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, andpreferred-locale. - Each row stores an external claim, required flag, and one transform:
identity,lowercase,csv-split, orfirst-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. -
groupsandtenant-roleremain 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/testand 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: falseso 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#
-
evaluateAuthChallengesupports 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
TenantAuthPolicyis 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/loginrequires 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_tokenandresponse_mode=form_post. -
POST /v1/auth/sso/callbackconsumes 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
provisionFederatedUserwhen 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.*ortenant.sso.probe.completedevents 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
503without 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: falseis 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-blockedwhen an IPv4-only CIDR cannot match it. - Replica switch during callback — process-local state may be absent on the receiving replica.
E2E coverage#
apps/oshun/web/e2e/tenant-sso-config.spec.tsdrives the operator roster, manual create, both imports, all mapping slots and transforms, edit/delete, and successful plus failed Test verdicts.apps/oshun/web/e2e/tenant-sso-crud-bff.spec.tsdrives CRUD, validation, import failure atomicity, probe outcomes, auth gates, and the admin audit chain over real HTTP. Its eight cases passed against an isolated current-source BFF, fresh migrated PostgreSQL database, and durable Redis at source commit257b390ac439866669a9952bde1c2be57d7f4975.apps/oshun/web/e2e/tenant-sso-oidc-login-bff.spec.tsdrives signed-token OIDC login, transformations, JIT/existing-user behavior, state and flow failures, missing claims, and untrusted keys.apps/oshun/tenant-admin/e2e/tenant-identity.spec.tsdrives session gating, tenant-isolated roster read-back, sample policy evidence, empty/error states, and accessibility.apps/oshun/web/e2e/admin-auth-policy-bff.spec.tscovers a separate studio evaluator. It is not evidence of a tenant auth-policy editor.
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#
TENANT_ADMIN_WALKTHROUGH/identity.md— tenant-scoped read-only roster and fixed engine samples.operator/operator-sso.md— privileged connection editor and probe.operator/operator-tenant.md— membership console; it does not embed SSO setup.shell/04-auth-session.md— customer session boundary after OIDC callback.operator/operator-audit.md— searchable SSO management events.
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 → disabledstate? - Where should a persisted per-tenant auth policy live, and which runtime requests must enforce it?
- How do mapped
groupsandtenant-rolevalues 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?