SCIM 2.0
A service provider that passes both Okta CRUD and the Microsoft SCIM Validator — absorbing both Entra PATCH dialects.
SCIM 2.0 (RFC 7642/7643/7644) is the provisioning wire protocol, and the only
real test of a SCIM service provider is that both Okta and Microsoft Entra
drive it cleanly — and they disagree. Entra sends a capitalized op, can encode
active as the string "False", and uses a no-path multi-attribute
replace; Okta sends a no-path replace with a boolean. The Tessera SCIM
endpoint absorbs all of it with one PATCH engine over a canonical attribute tree.
Two rules prevent the classic failures: active:false is a soft delete (the
user stays GET-able), and an unknown-user filter returns a 200 empty
ListResponse, never a 404 — which is exactly what Entra’s “Test Connection”
probe checks. Everything is served as application/scim+json over TLS 1.2+.
Code
// Absorb both IdP dialects: Entra sends capitalized `op` and (legacy)
// `active` as the STRING "False"; Okta sends a no-path replace. One
// PATCH engine handles add/replace/remove over a canonical attribute tree.
fn normalize_op(op: &str) -> Op { op.to_ascii_lowercase().parse().unwrap() }
fn parse_active(v: &serde_json::Value) -> bool {
match v {
serde_json::Value::Bool(b) => *b,
serde_json::Value::String(s) => !s.eq_ignore_ascii_case("false"),
_ => true,
}
}
// active=false is a SOFT delete: the user stays GET-able (never hard-delete).
// Unknown user filter to 200 empty ListResponse (never 404); counts are integers.Standards it follows
- SCIM Definitions, Overview, ConceptsRFC 7642
- https://www.rfc-editor.org/rfc/rfc7642
- SCIM Core SchemaRFC 7643
- https://www.rfc-editor.org/rfc/rfc7643
- SCIM ProtocolRFC 7644
- https://www.rfc-editor.org/rfc/rfc7644
Best practices applied
- Normalize `op` case-insensitively and accept `active` as a boolean AND the string "False" (Entra legacy dialect). source
- Handle `replace` both with and without `path`, and group-member removal as both a value array and `members[value eq "..."]`. source
- Never hard-delete on `active:false` — keep the resource GET-able (soft delete). source
- Match by `userName` AND `externalId`; a zero-result filter returns a 200 empty ListResponse, never 404; counts are integers. source
- Serve `application/scim+json` over TLS 1.2+ with a public CA, and statically compile /Schemas, /ResourceTypes, /ServiceProviderConfig. source