Implements: PRD 0001
Problem
PRD 0001 sets the bar for selling the platform to an enterprise. Its identity row asks for single sign-on through SAML and OpenID Connect, SCIM provisioning and deprovisioning of people and groups, an enforced second factor and session limits set by the company. Its organisation row asks for departments, teams and roles from the identity provider, and several business units in one account, kept apart where they need to be.
RFC 0027 started this for teams: OpenID Connect per team on a verified domain, then SCIM, then SAML, with the choice of how to do SAML and SCIM left open until a contract needed it. A company evaluating Atlas is that contract. Its security team asks five questions before anyone signs in:
- Can our people sign in with the accounts they have? Through the company's own provider (Okta, Microsoft Entra ID, Google Workspace, an older SAML-only one), with its passwords, second factor and device checks, started from our page or from the provider's own app launcher.
- Does leaving the company end access here? Deactivating someone in the directory must remove them, their sessions and their roles without anyone remembering to.
- Do our rules hold here? Second factor required, sessions no longer than the company allows, a fresh sign-in before anything sensitive.
- Is our structure kept? A group in the directory becomes a role and a place in the company's tree, and one business unit cannot read another's work when it should not.
- Is it on record, and does it go away? Every sign-in, provisioning call and policy change in the audit log and in the company's SIEM; and erasure that still works.
What exists today: team accounts with
owner, admin and member roles; a security policy with "two-factor required" (checked as
"a second factor is set up") and allowed e-mail domains; domains verified by DNS for one
account at a time; a team audit log streamed as audit.event (RFC 0022); API tokens
that are verified at the edge like any other token (RFC 0016); and erasure of a person
across every service. What is missing is the company's identity provider in the loop,
its directory, rules the company sets about sessions, and any structure below the team.
Proposal
One organisation (today's team account, org in every token) gets an identity section
the company's owner and admins manage: connections, a directory, a policy, business
units and break-glass accounts. Five rules hold throughout:
- The edge stays the only place a token is verified. Nothing below it learns a new way to authenticate. The company's identity provider is one more upstream provider to our sign-in, the SAML bridge sits beside our identity provider and never in front of an API, and the directory's SCIM calls carry a token the edge verifies.
- Default deny. A connection does nothing until its domain is verified and an owner turns it on; a SCIM token has one scope and nothing else; a group maps to no role until an admin maps it; a business unit inherits nothing it was not given.
- Secrets live sealed or not at all. The client secret of a company's OIDC application is sealed at rest like a webhook secret, and the copy our identity provider needs lives only in the platform's secret store. Our SAML signing key is in the secret store. An identity provider's certificate is public; we keep it and its fingerprint. A SCIM token is an API token: we keep its row, never the token or a hash that could recreate it.
- Every identity event is an audit line: who, what, outcome, never content. An e-mail address in an audit line is a fingerprint, as it is for invitations today.
- Erasure keeps working. Every new row about a person (external ids, group memberships, unit memberships, break-glass enrolment) goes with the person's erasure; deactivation by SCIM is not erasure and never pretends to be.
The recommendation
One self-hosted, open-source SAML bridge in front of our identity provider for SAML, plain OpenID Connect from our identity provider for everything else, and SCIM implemented in our own accounts service behind the edge.
This keeps the one rule that matters most here, the edge as the only verifier, at the cost of running and patching one more component and writing SCIM ourselves. The trade-off is below, under Alternatives.
Connections: SAML 2.0 and OpenID Connect per organisation
- Verified domain first. A connection is tied to one or more of the organisation's verified domains (RFC 0030). A domain is verified for one account at a time, so an address routes to at most one organisation. When a domain lapses, its connection stops routing and the organisation falls back to break-glass sign-in, as RFC 0030 already says for single sign-on.
- OpenID Connect (OpenID Connect Core 1.0). The admin gives the issuer, client id and secret. We read the
issuer's discovery document, check it serves
authorization_codewith PKCE, and test a sign-in before the connection can be turned on. - SAML 2.0 (OASIS SAML 2.0). The admin uploads the identity provider's metadata (or its URL). We answer with ours: entity id, assertion consumer URL, our signing certificate. Signed assertions are required; encrypted assertions are offered; the NameID must be an e-mail address in a verified domain or a persistent id with the address as an attribute.
- Both directions. SP-initiated: the person types a work address on our sign-in page and is sent to the company's provider (home realm discovery). IdP-initiated: the person clicks our tile in the company's launcher, and the bridge turns that into the same SP-initiated flow, so every session starts from a request we made.
- Just-in-time provisioning. A first sign-in through a connection makes the identity and, when the organisation allows it, a membership with the organisation's default role (member). Without SCIM, the provider's group claim, when sent and mapped, sets the role at each sign-in; with SCIM, the directory decides and the claim is ignored.
- SSO required. "SSO required" sits beside "two-factor required" in the policy. A member whose session did not come through the organisation's connection is refused the organisation, with a link to sign in through it. It covers admins too; the named break-glass accounts are the only exception, and every use of one is audited (decided by the owner, 2026-10-07).
SCIM 2.0: people and groups from the directory
- The protocol: RFC 7643 for the schema,
RFC 7644 for the protocol, and only the subset
the large directories call. Microsoft Entra ID needs
PATCHon users and groups (itsopvalues in any case), lookups byuserName eq,externalId eqand, for groups,displayName eq,excludedAttributes=members, paging, soft deletion throughactive=falseand/Schemas, with one bearer token (Entra's SCIM guide). Okta needsfilter=userName eq "…", four base attributes, and bothPUT(user updates) andPATCH(activation, deactivation, group membership) (Okta's SCIM 2.0 guide, preparing an integration). No/Bulk. Validated against Entra's SCIM Validator and Okta's test suite before the slice ships. - The token: an API token (RFC 0016) with the single scope
directory:provision, made by the owner or an admin, bound to the organisation, with a lifetime the admin chooses. The edge verifies it; we keep its row, never the token. Revoking it stops the directory at once. - People:
active: falseorDELETEremoves the membership, revokes the person's sessions and remembered consents for the organisation's clients, and writesscim.user.deactivate. It does not erase the person: they may belong to another account, and erasure is the person's or the account's choice. - Groups: a group maps to a role (owner excluded: ownership never moves by SCIM), to a business unit, or to both, in a mapping table an admin edits. An unmapped group is stored and grants nothing. A person in several mapped groups gets the highest role per unit.
Second factor, enforced, with a view of who complies
- Enforced, not only "set up". Today's check asks whether a second factor exists. The stronger check asks whether this session used one: the token carries the session's assurance level, and the organisation's one role check refuses a session below it, sending the person to step up rather than to a dead end.
- Methods: authenticator app (TOTP), security keys and passkeys (WebAuthn), and recovery codes as the way back. The policy can require phishing-resistant methods only (WebAuthn and passkeys).
- Through SSO: the company's provider usually does the second factor. When the
organisation says its provider enforces one, the provider's assertion of it (an
mfamethod in OpenID Connect, the authentication context in SAML) satisfies the policy and we do not prompt again (decided by the owner, 2026-10-07). Without that statement, the policy asks for ours. - The admin view: a page listing every member with the methods they have set up, whether their last session used a second factor, and through which connection they signed in, filterable and exportable, and the same as an API read.
Session policy per organisation
- Lifetime: the longest a session may serve the organisation, and the longest it may sit idle, within the platform's own bounds. A token whose sign-in time is older than the organisation allows is refused for that organisation, and the person signs in again; their personal account and other organisations are not affected.
- Fresh sign-in for sensitive actions: a list the platform fixes (change the policy, the connections, the SCIM token, break-glass, roles of admins, export, delete) plus any the organisation adds from the catalogue. Each requires a sign-in within the last few minutes, set per organisation; an older one gets an answer that tells the client to ask for a fresh sign-in, and the client does.
- Sign out everywhere for one person or the whole organisation, from the admin view, through the same revocation the console's device list uses today.
Business units
A business unit is a node in a tree under one organisation: a subsidiary, a division, a department, a team. Several units live in one account, kept apart where they need to be, which is the PRD's organisation row.
- Shape: a unit has a parent (or the organisation), a name, a kind (business unit, department, team) and an optional manager. Depth is bounded.
- Roles inherit downwards: an admin of a unit is an admin of everything under it; a member of a unit reads what is shared with the unit and its ancestors' shared spaces. Organisation owners and admins see the whole tree.
- Kept apart: a unit marked
isolatedstops inheritance from above for everyone but the organisation's owner and break-glass accounts: an organisation admin outside the unit sees its name and its size, not its contents, until a unit admin grants access. Every grant is an audit line. - From the directory: SCIM groups map to units (above), so the company's directory keeps the tree; manual edits are for what the directory does not know.
- What it is not yet: a unit is the first attribute a later access-control model reads (with department, clearance and region). That model, attribute-based access with data classes, is its own RFC under PRD 0001's access-control row, not this one. This RFC only puts the unit into the principal, so that RFC finds it there.
Break-glass
The company's identity provider can fail, be misconfigured, or be the thing under attack. The organisation keeps a way in that does not depend on it.
- Who: the owner, plus at most one more account the owner names. Both must have a phishing-resistant second factor (a passkey or security key) and sign in with our own credentials, never through the connection.
- What it opens: everything the owner may do, for a short session, with every sensitive action needing a fresh sign-in.
- Loud by design: using it writes
breakglass.used, notifies every other admin of the organisation and the security contact, and shows a banner in the console until an admin reviews and closes the event. - Not ours: InOrbit staff have no break-glass into a customer's organisation. The platform's own administrators read accounts and change none, as today.
Audit
Every identity event is an audit line in the organisation's log, an audit.event in its
stream to the company's SIEM, and an event in the catalogue (RFC 0022), carrying ids and
outcomes, never assertion contents, tokens, certificates or addresses in clear:
| Family | Events |
|---|---|
sso. |
connection.create, connection.update, connection.test, connection.enable, connection.disable, connection.delete, signin (outcome, method, connection, idp- or sp-initiated), signin.refused (reason code) |
scim. |
token.create, token.revoke, user.create, user.update, user.deactivate, user.delete, group.create, group.update, group.delete, mapping.update |
mfa. |
policy.update, stepup.required, stepup.done, refused |
session. |
policy.update, expired, reauth.required, revoke (one person or all) |
unit. |
create, update, move, delete, member.add, member.remove, isolate, grant, revoke |
breakglass. |
enrol, remove, used, reviewed |
Erasure
- A person's erasure (the accounts sweeper, which asks every service to erase the subject) also deletes their external ids per connection, their SCIM user record, their group and unit memberships and their break-glass enrolment; audit lines keep the fingerprint only.
- An organisation's deletion deletes its connections (including the bridge's tenant and its copy of the provider's metadata), its SCIM tokens, groups, mappings, units and policy, after the usual grace period.
- The bridge keeps no assertion after a sign-in completes; what it keeps per tenant is configuration, which goes with the connection.
API sketch
A new service in the accounts package, served by the accounts service, because
memberships, roles, domains and erasure already live in its store and change in one
transaction. REST through the protocol's transcoding (RFC 0033's paging on every list),
gated at the edge like the rest of /v1/accounts/ (owner or admin of the organisation,
checked in the one role check), SCIM under its own scope.
// proto/iohr/accounts/v1/identity.proto (proposal, not built)
service OrgIdentityService {
// Connections
rpc ListConnections(ListConnectionsRequest) returns (ListConnectionsResponse); // GET /v1/accounts/orgs/{org_id}/connections
rpc CreateConnection(CreateConnectionRequest) returns (CreateConnectionResponse); // POST /v1/accounts/orgs/{org_id}/connections (oidc | saml)
rpc UpdateConnection(UpdateConnectionRequest) returns (UpdateConnectionResponse); // PATCH /v1/accounts/orgs/{org_id}/connections/{connection_id}
rpc TestConnection(TestConnectionRequest) returns (TestConnectionResponse); // POST /v1/accounts/orgs/{org_id}/connections/{connection_id}/test
rpc DeleteConnection(DeleteConnectionRequest) returns (DeleteConnectionResponse); // DELETE /v1/accounts/orgs/{org_id}/connections/{connection_id}
rpc GetServiceProviderMetadata(GetServiceProviderMetadataRequest) returns (GetServiceProviderMetadataResponse); // GET …/connections/{connection_id}/sp-metadata
rpc ResolveDomain(ResolveDomainRequest) returns (ResolveDomainResponse); // internal: the sign-in pages' home realm discovery
// Policy (extends today's team policy)
rpc GetIdentityPolicy(GetIdentityPolicyRequest) returns (GetIdentityPolicyResponse); // GET /v1/accounts/orgs/{org_id}/identity-policy
rpc SetIdentityPolicy(SetIdentityPolicyRequest) returns (SetIdentityPolicyResponse); // PUT /v1/accounts/orgs/{org_id}/identity-policy (fresh sign-in)
rpc ListMemberFactors(ListMemberFactorsRequest) returns (ListMemberFactorsResponse); // GET /v1/accounts/orgs/{org_id}/members/factors
rpc RevokeSessions(RevokeSessionsRequest) returns (RevokeSessionsResponse); // POST /v1/accounts/orgs/{org_id}/sessions/revoke
// Directory
rpc ListGroupMappings(ListGroupMappingsRequest) returns (ListGroupMappingsResponse); // GET /v1/accounts/orgs/{org_id}/directory/groups
rpc SetGroupMapping(SetGroupMappingRequest) returns (SetGroupMappingResponse); // PUT /v1/accounts/orgs/{org_id}/directory/groups/{group_id}
// Business units
rpc ListUnits(ListUnitsRequest) returns (ListUnitsResponse); // GET /v1/accounts/orgs/{org_id}/units
rpc CreateUnit(CreateUnitRequest) returns (CreateUnitResponse); // POST /v1/accounts/orgs/{org_id}/units
rpc UpdateUnit(UpdateUnitRequest) returns (UpdateUnitResponse); // PATCH /v1/accounts/orgs/{org_id}/units/{unit_id} (rename, move, isolate)
rpc DeleteUnit(DeleteUnitRequest) returns (DeleteUnitResponse); // DELETE /v1/accounts/orgs/{org_id}/units/{unit_id}
rpc SetUnitMember(SetUnitMemberRequest) returns (SetUnitMemberResponse); // PUT /v1/accounts/orgs/{org_id}/units/{unit_id}/members/{subject}
// Break-glass
rpc ListBreakGlass(ListBreakGlassRequest) returns (ListBreakGlassResponse); // GET /v1/accounts/orgs/{org_id}/break-glass
rpc SetBreakGlass(SetBreakGlassRequest) returns (SetBreakGlassResponse); // PUT /v1/accounts/orgs/{org_id}/break-glass/{subject}
rpc ReviewBreakGlassUse(ReviewBreakGlassUseRequest) returns (ReviewBreakGlassUseResponse); // POST …/break-glass/uses/{use_id}/review
// SCIM 2.0, called through the protocol's /scim/v2 translation (scope directory:provision)
rpc ScimUsers(ScimUsersRequest) returns (ScimUsersResponse); // /v1/accounts/orgs/{org_id}/scim/v2/Users[/{id}]
rpc ScimGroups(ScimGroupsRequest) returns (ScimGroupsResponse); // /v1/accounts/orgs/{org_id}/scim/v2/Groups[/{id}]
rpc ScimDiscovery(ScimDiscoveryRequest) returns (ScimDiscoveryResponse); // ServiceProviderConfig, ResourceTypes, Schemas
}
New scopes, deny by default (RFC 0016): identity:read, identity:write (connections,
policy, units, mappings) and directory:provision (SCIM only). The sign-in pages call
ResolveDomain as a service through the internal listener, never from the browser
(RFC 0079 identifies them).
Plan, in slices
Each slice is useful alone and ships behind the organisation's own switch.
- OpenID Connect for one organisation on a verified domain. Connection CRUD for
OIDC, home realm discovery, just-in-time membership, "SSO required" with the owner as
break-glass,
sso.*audit lines. Proved on our own organisation with Google Workspace first. This is RFC 0027's step 1 at organisation level. - Claims and session policy.
aal,amr,auth_timein the token; the one role check reads them; session lifetime, idle limit and fresh sign-in for the fixed list of sensitive actions;session.*. - Enforced second factor and the admin view. Session-level enforcement, the
phishing-resistant option, trusting the provider's second factor or not, the member
factors page and API;
mfa.*. - SAML. The bridge deployed with its own sealed configuration; SAML connections, our metadata, signed assertions required, SP- and IdP-initiated sign-in; tested against two providers' SAML apps.
- SCIM. Users and Groups, the
directory:provisiontoken, deactivation that revokes sessions, group-to-role mappings; passed against the providers' SCIM validators. - Business units. The tree, inheritance,
isolated, group-to-unit mappings, units in the principal;unit.*. - Break-glass, full form. The second named account, notifications, the banner and
the review;
breakglass.*.
Slices 1 to 3 need no new component. Slice 4 is the first that runs the bridge.
Out of scope
- Attribute-based access and data classes (PRD 0001's access-control row: roles plus department, clearance and region; public, internal, confidential and restricted classes; visibility per section). Owned by its own RFC; this one only puts units into the principal.
- Routing, approval chains and separation of duties for Atlas questions and documents: they consume units and roles from here.
- The rest of PRD 0001's enterprise table: where the agent runs, model choice, residency, retention and legal hold, keys the customer holds, the SIEM stream's own delivery guarantees, Slack, Teams, Jira, ServiceNow and Confluence, reporting, legal and support.
- Our own staff's access to our platform (emergency access for our operators is a separate procedure in the compliance programme).
Controls this touches
The design is meant to line up with SOC 2's CC6.1 (logical access: SSO, enforced second factor, session policy), CC6.2 (registering and removing users: just-in-time membership, SCIM provisioning and deprovisioning) and CC6.3 (role-based access and its changes: group mappings, units, admin view), and with ISO/IEC 27001 (2022 edition) Annex A 5.15 (access control), 5.16 (identity management), 5.17 (authentication information), 5.18 (access rights) and 8.5 (secure authentication). In our own controls register it strengthens "Customer team access controls", "Role-based access to platform surfaces, with logged role changes", "API keys and API tokens as scoped, expiring, revocable credentials", "Audit trail of API calls" and "Account erasure across services", and it adds a customer break-glass procedure next to our own "Emergency access procedure". None of this is a claim of compliance or certification; an auditor attests, we align.
Alternatives considered
In short: a commercial licence of our identity provider's own enterprise edition, a second full identity provider as a broker, and a SAML library inside our own services were weighed; each either moves authentication out of the two places it lives today or moves a security-critical dependency onto a licence before a customer pays for it.
Decision
Open while nothing is built. Four questions were decided by the owner on 2026-10-07:
- The SAML bridge is self-hosted. We run the open-source bridge ourselves and do not buy the identity provider's enterprise licence for now.
- "SSO required" covers admins too. Only the named break-glass accounts sign in outside the connection, and every use of one is audited.
- An organisation may trust its provider's second factor. When the organisation
says its identity provider enforces one, the provider's assertion of it (
amrin OpenID Connect, the authentication context in SAML) satisfies the policy, with no second prompt from us. - SCIM deletion removes, never erases. When the directory deletes a person who has no other account, they are removed from the organisation only; erasure stays the person's or the account's own request.
Still open: which plans get which slices (for example OIDC single sign-on on Team, and SAML, SCIM, units and session policy on Enterprise only). That belongs to the pricing work for PRD 0001 and is decided there.
Publication
The console's organisation settings gain Connections, Directory, Second factor,
Sessions, Business units and Break-glass. The developer docs gain a page per provider
(Okta, Microsoft Entra ID, Google Workspace, a generic SAML 2.0 provider) with each
provider's screens, and the SCIM reference. The audit log gains the sso., scim.,
mfa., session., unit. and breakglass. families, which also stream as
audit.event. RFC 0027 points here for its organisation-level form.
Status log
- 2026-10-07: Opened for PRD 0001's enterprise identity row, taking RFC 0027 to organisations: SAML and OpenID Connect per organisation on verified domains, SCIM, enforced second factor, session policy, business units, break-glass, audit and erasure. Recommendation: one self-hosted SAML bridge, SCIM in our own accounts service, the edge still the only verifier. Owner decision pending.
- 2026-10-07: Decided by the owner: the SAML bridge is self-hosted, no enterprise licence for now; "SSO required" covers admins, only named break-glass accounts are outside it and each use is audited; an organisation may trust its provider's second factor with no second prompt; a SCIM deletion removes the person from the organisation and never starts erasure. Which plans get which slices stays open with the pricing work for PRD 0001. Status stays open until a slice is built.
- 2026-10-07: which plans get which identity slices (the owner, for Atlas A7, RFC 0085): Team gets single sign-on with OIDC for one organisation on a verified domain, enforced second factor with the admin view, and break-glass. Enterprise gets every slice, adding SAML through the bridge, SCIM, business units and session policy.