Identity Federation and SSO
Connecting an existing identity provider is usually the first integration a customer does, and System9 treats it as a first-class, governed object rather than a configuration file. A federated provider is a registry row with a lifecycle, a required conformance test before it can be switched on, its own audit chain, and its own replication policy.
The design commitment underneath is worth stating first, because everything else follows from it:
Federation is additive. Local authentication is the baseline, not a failure mode. A deployment with zero providers is a complete, fully functional deployment. A deployment with ten providers still has a local administrator path that no policy, and no enforcement mode, can remove.
Why this is more than a login page
The login screen is the least interesting part. What matters is that a federated person lands as a directory entity, byte-identical to one created in the console, and therefore shares one authorization model with every machine, device and agent in the estate.
The customer keeps their identity provider and their joiner-mover-leaver process. What they gain is that their people, their service accounts, their devices and their agents are all principals in the same directory, evaluated by the same grant machinery. A federated user and an agent acting on their behalf resolve into one delegation chain, which is the part that does not exist if identity and agent authority live in different systems.
What you can connect
Two orthogonal choices describe a provider: the protocol (the engine that runs the exchange) and the kind (the vendor preset that pre-fills claim defaults).
| Provider kind | Protocol | Notes |
|---|---|---|
| Microsoft Entra ID | OIDC | Full flow with a vendor claim preset; multi-tenant issuer templates supported |
| Okta | OIDC | Vendor claim preset. Note Okta conventionally carries roles on the scp claim, not roles |
| Google Cloud | OIDC | Vendor claim preset |
| Ping Identity | OIDC | Runs on the generic OIDC engine |
| AWS IAM Identity Center | OIDC | Runs on the generic OIDC engine |
| Generic OIDC | OIDC | Anything else that speaks OIDC, including Auth0, Keycloak, JumpCloud and OneLogin |
| Generic SAML 2.0 | SAML | Full assertion-consumer, metadata and single-logout surface |
| Active Directory / LDAP | LDAP | Direct bind against a domain controller. A credential form on the login page rather than a redirect, and the provider is resolved server-side, so one form covers every configured directory |
| CAC / PIV | Direct mTLS | Smartcard authentication without an identity provider in the path. Export-controlled; see CAC and PIV |
A SAML deployment of Entra ID, Okta or Ping registers as Generic SAML 2.0, there is no vendor-specific SAML kind.
Providers are scoped either org (replicates to every node in the
organization) or node (local to one node). Scope, organization, node and
slug are fixed at creation and cannot be edited afterwards.
The shape of a federated login
Figure: a federated login, end to end. Cyan stages carry short-lived single-use material; the two amber stages are gates that can refuse and are the only way to reach the green stage. No session exists until every gate has passed. Ceiling: the diagram shows the OIDC path; SAML substitutes assertion validation for token validation at the same choke point and is otherwise identical from the match stage onward.
Table equivalent
| Stage | What happens | Refuses when |
|---|---|---|
| Login | state, nonce and a PKCE S256 challenge are minted and stored | The return-to target is not a relative path |
| Identity provider | The person authenticates at their own IdP | Per the IdP's own policy |
| Callback | state is consumed by an atomic single-use delete | The state is unknown or already used, or the echoed issuer does not match |
| Validation | Signature, algorithm, issuer, tenant, audience, authorized party, nonce | Any one of them fails; see validation |
| Match or provision | The matching policy classifies the subject, then the admission gate runs | No match under a reject policy, or the admission gate denies |
| Assurance floor | The returned authentication context is compared to the effective floor | The context does not clear the floor |
| Session | A session is minted on the same path local login uses | Does not refuse |
What the validation gate checks
Validation runs before any session is minted, and it never degrades. It is a single choke point rather than checks scattered through the router, so there is no path to a session that skips it.
| Check | Behaviour |
|---|---|
| Signature algorithm | Asymmetric only (RS256, ES256, PS256). HS* and none are never accepted, which closes the algorithm-confusion class |
| Key source | Keys resolve only from the pinned JWKS endpoint, by key id |
| Embedded key headers | Tokens carrying jku, jwk, x5u or x5c are rejected outright |
| Size | A 64 KiB pre-parse cap on tokens, 256 KiB on SAML responses |
| Issuer | Must match the configured issuer, including the concrete instantiation of a multi-tenant template |
| Tenant | The tenant claim must appear in the allowed-tenant list when one is configured |
| Audience and authorized party | Both checked |
| Nonce | Must be present and match the one minted at login |
| Clock skew | Tolerated to a configured leeway, 60 seconds by default |
SAML adds an XML signature-wrapping defence that asserts exactly one assertion
in a validated protocol envelope, single-use InResponseTo correlation, an
assertion replay store, and audience, recipient, destination and time-window
checks. If the replay store is unavailable the login is refused, rather than
proceeding unchecked.
The provider lifecycle, and the gate that guards it
A provider moves through six states, and the transitions are enumerated rather than implied.
Figure: the provider lifecycle. A provider can only be deleted from draft or archived, never while it might be carrying logins. Any edit to the configuration of an enabled provider drops it back to testing, and it cannot return to enabled without a fresh passing test over the edited configuration.
Table equivalent
| State | Meaning | Can it serve logins? |
|---|---|---|
| Draft | Created, configuration incomplete | No |
| Testing | Configured, awaiting a passing conformance test | No |
| Enabled | Serving | Yes |
| Disabled | Switched off; provider-bound sessions were drained on the way | No |
| Decommissioning | Being retired, reversible | No |
| Archived | Retired, record retained for audit | No |
The enable gate
A provider becomes enabled only when a stored test result exists that
- passed,
- ran more recently than the last change to the core configuration, and
- was run against a configuration hash equal to the hash of the configuration as it stands now.
The third condition is the interesting one. Without it, an operator could pass a test, edit the configuration, and enable the edited version on the strength of a test that never saw it. Hashing the canonical configuration into the test result closes that window.
The practical consequence for operators: editing an enabled provider takes it out of the login set. The edit is accepted, the state drops to testing, and re-enabling reports a configuration-hash mismatch until a fresh test is run. An operator who tweaks a scope list and walks away has silently removed a login path, so treat a configuration edit as a change window.
Mapping an identity to an entity
A federated subject becomes a directory entity through three declarations, each stored per provider.
Claim mapping and the identity key
Claim mappings translate what the provider asserts into platform attributes:
entity_match_key, email, display_name, given_name, family_name,
groups, roles. Transforms such as lowercasing or stripping a domain prefix
are applied on the way through.
Exactly one mapping per provider is the identity key, and it must be over an immutable claim. Email is rejected as an identity key, deliberately: email addresses are reassigned, and an identity key that can be reassigned is a way to inherit somebody else's entity. The conventional choices are the Entra object id, the SCIM external id, a SAML persistent NameID, an Active Directory object GUID, or a PIV FASC-N.
The value of that claim becomes the subject key, and the link it forms to an entity id is what everything downstream, roles, workspace membership, audit, actually keys off. The identity provider's notion of the subject never leaks past this boundary.
Matching policy
The matching policy decides what happens when a subject arrives:
| Setting | What it controls |
|---|---|
| Match attributes | An ordered list, for example email then UPN then username |
| Normalization | Exact, case-insensitive, or trimmed |
| Require verified | Whether an unverified claim may be matched on at all (on by default) |
| Match action | auto_link, review, or reject |
| Create if no match | Whether an unmatched subject may be provisioned (off by default) |
| Dual control | Whether a link requires a second approver |
| Orphan policy | Keep, suspend, or delete an entity whose subject disappears upstream |
A review outcome denies the login and queues the subject for an operator
decision, storing a hash of the subject key rather than the key itself.
Approvals are single-use.
Just-in-time provisioning
With just-in-time provisioning enabled, a first successful login mints the
entity, a user for a human provider, a service_account for a workload
provider, through the same identity pipeline the console uses. It passes the
platform's provisioning admission gate first, so a provider cannot become an
unbounded source of new principals.
Group and role mapping
A mapping says: this group at the identity provider grants this role at this scope, and scope is one of exactly three: the organization, a node, or a workspace. Workspace-scoped mappings are how a federated person becomes a workspace member.
Two properties are worth knowing:
- Federation only ever manages its own grants. Every grant a mapping produces is tagged with the provider that produced it, and reconciliation revokes only within that tag. A grant an administrator made by hand is never touched by a federation sync.
- You cannot map a group onto a governance or high-privilege role. The attempt is refused rather than applied. An identity provider's group membership is not an administrative escalation path, and every grant passes the admission gate regardless.
SCIM provisioning
System9 is a SCIM 2.0 service provider: the identity provider pushes users and groups to it. Tokens are administered from the provider's SCIM tab in the console, or over the operator API. The surface is per provider, and it is authenticated by a provider-scoped bearer token and nothing else, no session, no system token, no client certificate. A token minted for one provider cannot drive another's endpoint.
| Property | Behaviour |
|---|---|
| Resources | Users and Groups, with create, read, update, patch and delete |
| Not offered | Bulk operations and password change, both advertised as unsupported |
| Page size | Capped at 200 results |
| Passwords | Dropped on write; the platform never accepts a password over SCIM |
| Deprovisioning | Runs the standard entity lifecycle cascade, suspend, restore, soft delete |
| Tokens | Stored as hashes only, with rotation lineage and a default one-year expiry. The raw value is shown once, on issue or rotation, and never again |
Sync failure is handled by latching rather than by retrying forever: five consecutive failed cycles quarantine the provider's sync, and a quarantine sustained for four weeks auto-disables the provider. Re-enabling it then requires a fresh passing test, because the enable gate applies to every route back to enabled.
Assurance: requiring a strong authenticator
Assurance is how a multi-factor requirement reaches across a federation boundary. It adds no new authenticator, it conveys and verifies the one the identity provider used.
- Requested at login. The authentication-context values the provider binding declares are sent with the authorization request.
- Verified at callback. The context the provider returns is classified and compared against the effective floor, which is the higher of the organization policy floor and the provider's own binding. A binding can raise the floor and can never lower it.
Only two mechanisms classify as phishing-resistant: FIDO2/WebAuthn with relying-party binding, and PKI smartcard authentication with channel binding. TOTP, SMS, push with number matching and recovery codes are multi-factor but are not phishing-resistant, and a policy that requires phishing resistance will refuse them.
Passkeys are classified further: a device-bound passkey can reach the highest assurance level, a synced passkey is capped one level below it. Both are phishing-resistant, the ceiling is about whether the key can be exported, not about whether it can be phished. The device model map used for this classification is bundled with the product, so the classification works with no network call.
At the higher federation assurance levels the platform additionally requires a hardware-backed signing key for its own service-provider identity, and refuses a software-held key.
Sessions, logout and revocation
A federated session is minted on the same path as a local one, so it inherits cross-cell replication and the standard revocation machinery described in sessions across a node's cells. It is then stamped with the provider id and the provider's session identifier, which is what makes provider-driven logout possible.
| Trigger | Effect |
|---|---|
| OIDC back-channel logout | The provider posts a signed logout token; matching sessions are revoked |
| SAML single logout | Same, optionally narrowed to one session index |
| Disabling a provider | Provider-bound sessions are drained and its SCIM tokens revoked, in the same operation. The response reports how many sessions were revoked |
That third row is the one that matters operationally: disabling a provider is not just "no new logins", it ends the ones already running. Without it, switching off a compromised provider would leave a standing-access window the length of a session lifetime.
The session sweep on provider-disable and on back-channel logout enumerates up to 500 sessions. On a node carrying more than 500 live user sessions, a single sweep can leave some behind. Where that is a concern, follow a disable with a session review rather than assuming the sweep was exhaustive.
You cannot lock yourself out
Three guards run before any destructive transition changes state, so a refusal arrives instead of a broken estate:
| Guard | Refuses when |
|---|---|
| Local administrator path intact | The change would leave the node with no local administrator route |
| Mandated provider in use | The change would disable a provider the organization mandates |
| Last enabled provider | Advisory, not blocking: warns that the node returns to its local-authentication baseline |
Alongside them is break-glass, a deliberately separate front door that authenticates against local credentials, never against an identity provider, and is mounted outside the federation route tree so that a wedged assertion endpoint cannot block it. It requires a specific break-glass permission at the target node, it is audited and it raises a live event when used, and no enforcement mode can remove it, including a fully organization-mandated one.
One deliberate sharp edge: break-glass must still meet the organization's assurance floor. A password-only break-glass under a floor that requires a hardware authenticator is refused and asks for step-up. There is no side door below the floor, which means the floor is a real property of the deployment rather than a default that any emergency dissolves.
Sovereign and air-gapped
Every part of this works with no internet.
- Local authentication is the baseline, so an air-gapped deployment that federates with nothing is a normal deployment, not a degraded one.
- LDAP direct bind reaches a domain controller on the local network. TLS is required, and turning it off is refused unless the configuration carries an explicit air-gap acknowledgement, so "no TLS" is always a decision somebody recorded.
- Smartcard authentication needs no identity provider at all, see below.
- The passkey classification map is bundled, no metadata service is contacted.
- Break-glass and the never-lockout guards are unaffected by connectivity.
There is no local identity-provider mode: System9 consumes OIDC and SAML, it does not issue them for third parties.
CAC and PIV
Smartcard authentication comes in two shapes.
Through a federal identity provider. The card authenticates the person at an IdP that already accepts it, and System9 sees an ordinary OIDC or SAML login. This path is generally available and carries no export gate.
Direct mutual TLS. The node authenticates the card itself, with no identity provider in the path, which is the point for a disconnected or tactical deployment. Identity comes from the card's FASC-N or PIV UUID, never from a common name or email. Accepted certificate-policy object identifiers default to the federal and DoD PIV-authentication families and are configurable. Channel binding means this path reaches the highest assurance classification.
Two hard constraints:
- Revocation checking is mandatory and fails closed. With no revocation source reachable, every smartcard login is refused. The card being valid is not enough; the platform has to be able to confirm it has not been revoked.
- This surface is export-controlled and gated behind a military licence tier and a sovereign or tactical deployment profile.
The direct path runs through the node's gateway, which forwards the verified client certificate to the smartcard endpoint only from a trusted hop, and strips the header otherwise so it cannot be injected from outside.
Replication and audit
Federation configuration is organization-wide state, and the replication policy says so per table: providers, governance policy, claim mappings, group mappings, SCIM tokens, assurance bindings, subject links, matching policy, invitations and reviews all converge globally and cross-node. Three tables stay local to the cell that wrote them: the SCIM sync cursor, the federation audit chain, and test results.
The audit chain is append-only and hash-chained, and it carries a hard requirement worth calling out during setup: every provider must name a service entity as its audit actor. A provider registered without one cannot write login audit entries. Registering the identity-provider service entity is a required setup step, not optional metadata.
Related topics
- Connect an identity provider, the task runbook, with Entra ID worked end to end.
- Authentication, the crypto material every principal holds and the local authentication surface federation sits beside.
- Sessions across a node's cells, what a session is once it exists, and how revocation reaches every cell.
- The entity model, the principal a federated subject becomes.
- Authorization and delegation, what the roles a group mapping grants actually authorize.
- Platform replication, the per-table policy the tables above are declared under.