Capabilities

Enterprise SSO & multi-org

SAML 2.0 on both sides with a hardened XML-DSig pipeline, generic OIDC federation with JIT provisioning, SCIM 2.0 Users and Groups, email-domain org discovery, and delegated administration fenced in the domain layer.

“Enterprise readiness” is mostly four letters: SAML, SCIM, SSO, and the org chart. Sentinel ships all of it in v1 — because these are the features you cannot bolt on the week a big customer’s security questionnaire arrives.

The tenancy model

Realm → Organizations → Teams, with Groups as a role/policy-aggregation axis orthogonal to teams. A user belongs to exactly one realm and zero or more organizations — multi-org membership is native, not simulated. Org context is chosen at token-mint time and carried in the org claim; switching orgs mints a new token without re-authentication, and permission snapshots are computed per (user, org). Email uniqueness is realm-scoped; all ids are Guid v7.

SAML 2.0 — both roles

Nuvora.Nexus.Sentinel.Saml implements both sides of the protocol:

  • SP (inbound) — your realm logs in against a customer’s IdP: GET /auth/saml/{idpKey}/start, POST /auth/saml/acs, GET /auth/saml/metadata.
  • IdP (outbound) — legacy service providers SSO against you: GET|POST /saml/idp/sso (redirect and POST bindings, signed assertions), GET /saml/idp/metadata.

XML-DSig is one of the most CVE-prone surfaces in identity, and Sentinel treats it that way. Signature verification pins the per-connection certificate (the document’s KeyInfo is never consulted), allowlists algorithms (RSA-SHA256+; SHA-1 and HMAC rejected) and transforms (enveloped + c14n only — no XPath, no XSLT), requires exactly one same-document reference, and — after the cryptographic check passes — verifies that the signed element is reference-equal to the element about to be consumed. That last step is the signature-wrapping defense, and the recipe walks it line by line with the tests (Signature_wrapping_injected_assertion_is_not_consumed and nine siblings). XXE is killed centrally: DTDs are prohibited before any trust decision is made.

On top of the signature pipeline sit the protocol checks: conditions windows with bounded clock skew, audience restriction, bearer subject confirmation, InResponseTo tracking, an assertion-id replay cache, and IdP-initiated SSO off by default.

OIDC federation and JIT provisioning

Inbound OIDC federation (MapSentinelFederation) speaks PKCE code flow against any compliant issuer — Google, Entra, Okta are just configured issuers, not special-cased integrations:

  • GET /auth/idp/{idpKey}/start → redirect to the external IdP
  • GET /auth/idp/callback → code exchange, ExternalJwtValidator against a cached JWKS
  • POST /auth/idp/discover → org discovery from an email address

Per-realm IdentityProviderConfig entries define issuer, endpoints, scopes and a JIT provisioning policy: Disabled (only pre-existing linked users may enter) or CreateUsers with mapping rules — claim patterns that add the new user to organizations or teams, assign roles, or set attributes. External identities link to users via the federated-identity store, and every path emits its event: federation.login_success, federation.user_provisioned, federation.identity_linked.

Org discovery routes login by email domain: organizations claim domains (OrganizationDomain, kinds EmailDomain / Subdomain), and only verified domains route. ada@customer.com → customer org → that org’s default IdP, before any password field appears.

SCIM 2.0 provisioning

MapSentinelScim() mounts a SCIM 2.0 server at /scim/v2 with Users and Groups — groups included, because users-only SCIM is how deprovisioning gaps happen.

  • Full resource surface: GET/POST /Users, GET/PUT/PATCH/DELETE /Users/{id}, the same for /Groups, plus ServiceProviderConfig, ResourceTypes and Schemas.
  • Deleting a user is a soft deactivation; deleting a group is hard.
  • Filtering supports the attribute eq "value" clause that real IdPs (Entra, Okta) actually send — userName, externalId, displayName — and answers anything fancier with an honest 501 in the RFC 7644 error shape. ServiceProviderConfig declares exactly what is and isn’t supported.
  • Auth is per-organization bearer tokens (sct_…, SHA-256 at rest, shown once). A token is fenced to its organization — a customer’s IdP can provision their people and see nothing else. The store tests pin it (OrgScoping_TokenOrgCannotSeeOtherOrgsUsers).

Delegated administration

The Node-generation sentinel’s worst hole was a class-level admin gate: one permission check at the controller, cross-org access one forgotten filter away. Sentinel’s fix is structural: admin authorization is evaluated per-resource in the domain layer. SentinelAdminService never compares org ids itself — it sets the target’s org as AccessCheck.ResourceOrganizationId and lets the same AuthorizationEvaluator that guards your app decide. sentinel:org:manage is therefore fenced to the caller’s orgs by the evaluator’s cross-org rule; there is no controller where a missing check could reintroduce the hole.

sentinel:global:manage is the distinct realm-admin scope. The admin API (MapSentinelAdmin, default /sentinel-admin) covers orgs, users, suspend/reactivate, roles, grants, assignments, the audit browser and the POST /authz/inspect debugger — and the React admin console in Sentinel.Admin consumes exactly this API, authenticated through Sentinel itself.

Article 005 — Multi-org & delegated admin builds a two-org tenant with org-scoped admins and proves the fence holds.