# SCIM in practice: provisioning rarely breaks on create, it breaks on leave

Source: https://startwithidentity.com/blog/scim-in-practice-where-provisioning-breaks/
Last updated: 2026-10-21
License: content by Start with Identity. Cite the source URL.

---

SCIM provisioning almost never fails when a user is created. It fails when a user leaves, changes team, or loses a group, because those are the operations where [RFC 7643](https://www.rfc-editor.org/rfc/rfc7643.html) and [RFC 7644](https://www.rfc-editor.org/rfc/rfc7644.html) leave the most to the implementer. A connector can pass every onboarding test and still leave a departed employee with a live session, a group membership nobody removed, or a sync job that stopped weeks ago. If you test one path this quarter, test the leaver.

## Key takeaways

- `active: false` means whatever the application decides it means. The spec says so in as many words.
- The major identity providers deactivate rather than delete, so the app's handling of a disabled user is your real offboarding control.
- Group membership is where drift accumulates: nested groups that do not flow, removals that are read two ways.
- A provisioning job that keeps failing can slow down and switch itself off without anyone noticing.
- RFC 9865 (cursor pagination) and RFC 9967 (security event tokens) fix real gaps, but only once both sides implement them.

## "Disabled" is defined by the app, not the protocol

RFC 7643 defines `active` as "a Boolean value indicating the user's administrative status," and then: "The definitive meaning of this attribute is determined by the service provider." The typical example it gives is that `false` means the account is suspended. Nothing in the standard requires the application to end existing sessions, revoke API tokens, or stop OAuth grants when the flag flips.

Deactivation is the normal leaver signal. [Okta](https://startwithidentity.com/vendors/iam/okta/)'s [SCIM guide](https://developer.okta.com/docs/api/openapi/okta-scim/guides/scim-20/) says plainly that "Okta doesn't perform DELETE operations on user objects in your SCIM app"; it sets `active` to false instead. [Microsoft Entra ID](https://startwithidentity.com/vendors/iam/microsoft-entra/) [disables a user through an update](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/how-provisioning-works) when they are unassigned, go out of scope, or are soft-deleted, and sends a DELETE only once the user is hard-deleted, which happens 30 days after a soft delete unless someone purges them sooner.

So for most SaaS apps, offboarding through SCIM means one PATCH and then nothing for a month. If the app treats `active: false` as "hide from the user list" and leaves the personal access token working, the IdP's audit log says the user was deprovisioned and the app still accepts them. The September [CrowdSec disclosure](https://startwithidentity.com/blog/2026-09-19-crowdsec-kept-a-departing-engineers-github-access-open-and-lost-170-repos/) is the shape of the risk: a former employee's still-valid GitHub OAuth token was used to copy about 170 private repositories, [per The Hacker News](https://thehackernews.com/2026/09/crowdsec-says-tanstack-npm-attack-led.html). CrowdSec had chosen to keep that access open, so SCIM was not the cause, but the lesson carries: token lifetime, not account status, decides whether a leaver gets in.

## Groups drift in ways that are hard to see

Most SaaS entitlements ride on groups, and group sync has three documented traps.

**Nested groups often do not flow.** RFC 7643 lets a group contain groups. Entra's provisioning service "can't read or provision users in nested groups"; it only sees immediate members of an assigned group. If access is granted by nesting a team group inside an app group, the app never hears about those users, and when you later restructure, it never hears they left either.

**Membership changes only flow for pushed groups.** Okta sends membership updates only when the user is a member in Okta, is assigned to the integration, and the group is pushed. A group created in the app by hand, or one that stopped being pushed, is outside the loop from then on.

**Leaving a dynamic group is a deprovisioning event.** Entra notes this explicitly. A rule change that briefly drops half a department out of a dynamic group can disable them all downstream, and when they come back, Entra says it reactivates the user but does not restore dynamic group memberships automatically.

This is how [mover entitlement accumulation](https://startwithidentity.com/techniques/mover-entitlement-accumulation/) gets built: not by one failed request, but by groups that were never in scope of the sync in the first place.

## PATCH: one request, two correct readings

RFC 7644 section 3.5.2 says a `remove` operation on a multi-valued attribute without a filter removes "the attribute and all values." It does not define a `value` on remove at all.

Entra's [SCIM compliance notes](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/application-provisioning-config-problem-scim-compatibility) show what its default job sends to remove one group member: `"op": "Remove"`, `"path": "members"`, and a `value` array holding that member's ID. A server that follows the RFC literally sees "remove all members" and empties the group. A lenient server reads the value list and removes one person. Both can claim to be correct. The compliant form, available behind the `aadOptscim062020` flag, uses a filter path instead: `members[value eq "..."]`.

The same document lists other default behaviors that a strict server rejects or misreads, including disabling a user with the string `"False"` rather than the boolean `false`, and capitalized operation names such as `"Replace"`. Microsoft marks updating the default PATCH behavior as not yet fixed, with the date "TBD", and the flag does not work with on-demand provisioning. If you build a SCIM server, accept both forms and log which arrived. If you buy one, ask which it handles.

## Sync jobs stop quietly

Entra retries failed operations in later cycles, backing off as it goes. If most calls fail, for example because the app's API token expired, the job enters quarantine, incremental cycles slow to once a day, and after more than four weeks in quarantine the job is disabled. Email alerts exist, but only if someone configured them.

Microsoft's own example of a quarantine trigger is invalid admin credentials, which is what an expired or rotated provisioning token looks like from the IdP. Every leaver in that window keeps their account in the app, and nothing on the IdP side looks broken unless someone reads the provisioning logs. Treat the SCIM bearer token like any other [service account](https://startwithidentity.com/glossary/service-account/) secret: an owner, an expiry you know about, and an alert before it lapses.

## What the two new RFCs change

[RFC 9865](https://www.rfc-editor.org/rfc/rfc9865.html) (October 2025) adds cursor-based pagination to SCIM. Index-based paging over a large, changing directory can skip or repeat records between pages, which is a quiet source of reconciliation errors. Cursors let a server reuse its database's own paging and are advertised in `/ServiceProviderConfig`.

[RFC 9967](https://www.rfc-editor.org/rfc/rfc9967.html) (May 2026) defines SCIM events carried as Security Event Tokens (RFC 8417): create, patch, delete, activate and deactivate, plus asynchronous request completion. That gives receivers a push signal for a deactivation instead of waiting for the next poll, and fits alongside [CAEP](https://startwithidentity.com/glossary/caep/) and the [Shared Signals Framework](https://startwithidentity.com/glossary/shared-signals-framework/) for session revocation. Neither helps until both your IdP and your apps support it, so add both to vendor questionnaires.

## A leaver test plan you can run this week

1. Pick your ten most sensitive SCIM-connected apps. For each, create a test user, give them a session, a personal API token and an OAuth grant, then deactivate them in the IdP.
2. Within one sync cycle, check: can the session still load data? Does the token still call the API? Does the grant still refresh?
3. Remove the test user from a group, nested and direct, and confirm only that membership changed downstream.
4. Read the PATCH the app received. Note whether it used a value array or a filter path, and a string or boolean `active`.
5. Expire the app's provisioning token on purpose in a test tenant and time how long it takes anyone to notice.
6. Write down, per app, what `active: false` actually does, and put the answer in your [deprovisioning](https://startwithidentity.com/glossary/deprovisioning/) runbook.

Apps that fail step 2 need a second control in the offboarding workflow: an explicit session and token revocation call, or shorter token lifetimes. You cannot fix that in the IdP.

## Related reading

- [SCIM 2.0](https://startwithidentity.com/standards/scim-2-0/) on the standards hub and [what SCIM is](https://startwithidentity.com/guides/fundamentals/what-is-scim/)
- [SCIM provisioning implementation guide](https://startwithidentity.com/guides/implementation/scim-provisioning-implementation-guide/) and [user lifecycle management guide](https://startwithidentity.com/guides/implementation/user-lifecycle-management-guide/)
- [Orphaned account abuse](https://startwithidentity.com/techniques/orphaned-account-abuse/), the technique a failed leaver path enables
- [SCIM](https://startwithidentity.com/glossary/scim/), [joiner-mover-leaver](https://startwithidentity.com/glossary/joiner-mover-leaver/) and [orphaned account](https://startwithidentity.com/glossary/orphaned-account/) in the glossary
