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 IdPGET /auth/idp/callback→ code exchange,ExternalJwtValidatoragainst a cached JWKSPOST /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, plusServiceProviderConfig,ResourceTypesandSchemas. - 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.ServiceProviderConfigdeclares 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.