SCIM automates the lifecycle of user accounts. When an employee joins, their account is created automatically in the connected applications. When they leave, their accounts are disabled right away, without waiting for an administrator to think of it. Without SCIM, you manage accounts by hand in every application, with the security risks and the errors that come with it.
What you will learn
This guide explains SCIM, the standard for automatically synchronizing identities between the IdP and applications. By the end, you will be able to:
- Understand why SCIM is needed (and what the alternatives are)
- Master the SCIM operations: creation, update, deactivation
- Set up a clean offboarding (disable + revoke)
- Anticipate the common implementation pitfalls
Prerequisites: having read IAM basics (in French), OpenID Connect (in French) and SAML 2.0.
The problem: orphaned accounts
Provisioning is the creation and updating of accounts in applications; its opposite, deprovisioning, is their deactivation. Without automatic provisioning, here is what happens:
- An employee joins → manual account creation in every application
- The employee changes department → manual update of the groups
- The employee leaves → some accounts are forgotten and never disabled
Result: orphaned accounts that persist for months, even years. The risk is not theoretical: these accounts keep their rights, show up in no review because nobody claims them, and often escape MFA for lack of a user to enable it. They make an ideal entry point, with no owner to notice a suspicious login.
Provisioning approaches
Just-in-Time (JIT) provisioning
The account is created at first login through SSO (OIDC (in French) or SAML).
How it works:
- The user logs in for the first time
- The application receives the SAML assertion or the ID Token
- The application creates the account from the attributes it received
Advantages:
- Simple to set up
- No synchronization to maintain
Limits:
- The account only exists from the first login
- No pre-configuration (groups, specific permissions)
- No automatic deprovisioning
SCIM provisioning
The IdP pushes changes to the applications as soon as it detects them. The fundamental difference with JIT is this direction of flow: here, the IdP does not wait for the user to show up, it acts as soon as the data changes in the directory. This is what makes deprovisioning possible, since a departure is never followed by a login that could serve as a trigger.
How it works:
- A user is created/modified/disabled in the IdP
- The IdP sends a SCIM request to the application
- The application applies the change
Advantages:
- Accounts pre-created before the first login
- Synchronization of groups and attributes
- Automatic deprovisioning
Limits:
- The application must support SCIM
- More complex configuration
SCIM: the protocol
SCIM (System for Cross-domain Identity Management) is defined by two RFCs:
- RFC 7643: SCIM schema (User, Group objects)
- RFC 7644: SCIM protocol (REST API)
This split into two documents has a concrete consequence when you qualify an
application: SCIM support is not binary. A vendor can implement the schema
of RFC 7643 while offering only part of the operations of RFC 7644, typically
POST and PUT without PATCH. Always ask for the list of supported
operations, not just the "SCIM 2.0 compatible" claim.
SCIM architecture
The direction of the arrows is the important point of the diagram, and it often comes as a surprise: the IdP plays the client and the application plays the server. In other words, the central directory calls the REST API exposed by each application, not the other way around. This inversion explains two practical constraints: the application must publish a SCIM endpoint reachable from the IdP, and each application holds its own authentication token, which the IdP stores.
In the protocol's vocabulary, the IdP is the SCIM Client (it pushes changes) and the application is the SCIM Server (it receives and applies them).
SCIM objects
The schema of RFC 7643 defines two resources, User and Group, and a
standard extension EnterpriseUser (employee number, department,
manager). The User resource already carries multivalued roles and
entitlements attributes (§4.1.2): a simple role therefore needs no
extension. Only an attribute that is truly specific to an application
(license, in-house profile) goes through a vendor schema extension,
declared in the schemas array. So read the examples below by looking at that
array first: it tells you which grammar the application must be able to
interpret.
User
In the payload below, three fields drive the whole lifecycle:
externalId ties the account to the IdP, active carries the activation or
deactivation of the account, and userName serves as the login identifier.
The other attributes are display comfort. Note that id is not supplied by
the IdP: the application generates it and returns it at creation.
{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": "2819c223-7f76-453a-919d-413861904646", "externalId": "alice.martin", "userName": "alice.martin@example.com", "name": { "givenName": "Alice", "familyName": "Martin" }, "emails": [ { "value": "alice.martin@example.com", "type": "work", "primary": true } ], "active": true, "groups": [ { "value": "developers", "display": "Developers Team" } ], "meta": { "resourceType": "User", "created": "2024-02-06T10:00:00Z", "lastModified": "2024-02-06T10:00:00Z" }}Key attributes
Above all, remember the id / externalId pair: it is a classic
source of synchronization incidents. Each system keeps its own
identifier, and the IdP must remember the id returned by the application to
be able to update it afterwards. If this mapping is lost, the IdP no longer
knows which account to modify and creates duplicates instead of disabling
the existing one.
| Attribute | Description |
|---|---|
id | Unique identifier on the application side |
externalId | Unique identifier on the IdP side |
userName | Login identifier (often the email) |
active | Account enabled or disabled |
emails | List of email addresses |
groups | Groups the user belongs to |
Group
A Group object only contains its display name and the list of its
members, referenced by their id on the application side. A direct
consequence: adding someone to a group is done by modifying the group, not the
user. Many applications actually implement only the User resource and ignore
Group, in which case memberships must be handled another way, for example
through a claim passed at SSO login time.
{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "id": "group-123", "displayName": "Developers Team", "members": [ { "value": "2819c223-7f76-453a-919d-413861904646", "display": "Alice Martin" } ]}SCIM operations
CRUD on users
Two rows of this table deserve your attention. PUT replaces the whole
object: any attribute missing from the payload is erased on the application
side, which sometimes overwrites data the IdP does not know about. PATCH
only touches the listed paths, so it is the operation to favor for a targeted
change such as a deactivation. As for DELETE, it is marked "rare" on
purpose: most integrations never use it.
| Operation | HTTP method | Endpoint | Usage |
|---|---|---|---|
| Create | POST | /Users | New employee |
| Read | GET | /Users/{id} | Check an account |
| List | GET | /Users?filter=... | Search for users |
| Update | PUT | /Users/{id} | Full replacement |
| Partial update | PATCH | /Users/{id} | Targeted change |
| Delete | DELETE | /Users/{id} | Deletion (rare) |
Request examples
The three exchanges below cover the core of an integration: create,
disable, search. One detail links them: the content type
application/scim+json, required by RFC 7644. Many applications return a
415 Unsupported Media Type error if you send plain application/json, and
this is the first thing to check when provisioning fails without a clear
message.
Create a user:
POST /scim/v2/UsersContent-Type: application/scim+jsonAuthorization: Bearer <token>
{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "userName": "alice.martin@example.com", "name": { "givenName": "Alice", "familyName": "Martin" }, "emails": [{"value": "alice.martin@example.com", "primary": true}], "active": true}Disable a user (PATCH):
PATCH /scim/v2/Users/2819c223-7f76-453a-919d-413861904646Content-Type: application/scim+json
{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ { "op": "replace", "path": "active", "value": false } ]}Search for users:
GET /scim/v2/Users?filter=userName eq "alice.martin@example.com"The complete lifecycle
The three moments that follow have a name in the IAM vocabulary: Joiner, Mover, Leaver (arrival, job change, departure). It is the same data flow each time; only the SCIM operation changes. The starting point is always the HR system, never the IdP: if an account is created directly in the directory, it will fall outside the lifecycle and become an orphaned account.
Onboarding (arrival)
Step 4 is the one people forget to check. As long as the IdP has not stored
the id returned by the application, it will be able neither to modify nor
to disable that account later. An onboarding that "works" but whose response
was not recorded produces an account that nobody will be able to close when
the employee leaves.
-
Creation in the source system
The HR department records the new hire in its reference tool (Workday, Active Directory...).
-
Synchronization to the IdP
The central IdP (Keycloak, Okta) receives the user.
-
SCIM provisioning
The IdP pushes the user to the configured applications.
-
Confirmation
Each application returns a SCIM
idthat the IdP stores for future updates.
Job change (Mover)
A job change is a tricky scenario, because it requires
removing rights, not only adding them. A PATCH that adds the new group
without removing the old one lets the person accumulate the access of both
positions. Over internal moves, this accumulation yields accounts whose
rights go far beyond their position.
-
Group change in the IdP
The user changes team, their groups are updated.
-
SCIM PATCH to the applications
The IdP sends the group changes: add the new one, remove the old one.
-
Permission update
The application adjusts access according to the new groups.
Offboarding (departure)
Follow the order of the four steps closely: they go from the source to the
edges. Disabling in the IdP cuts nothing by itself, it only triggers the
PATCH requests to the applications. It is step 3, the token revocation,
that really ends the access in progress, and it is precisely the step many
applications do not implement.
-
Deactivation in the IdP
The account is marked
active: falsein the IdP, which refuses any new authentication from that moment on. -
SCIM PATCH: disable
The IdP sends
{ "active": false }to all the applications. -
Token revocation
The applications invalidate existing sessions and tokens.
-
Audit
The IdP and each application log the deactivation, so it can be proven afterwards.
Clean offboarding
An offboarding is clean when none of the five guarantees below is missing. The validation criterion is simple to state and rarely tested: a few minutes after the deactivation in the IdP, no authenticated request in that person's name should succeed any more, neither from a browser that is already open, nor from an API token that is still valid.
- The account is disabled (no new logins)
- Active sessions are terminated
- Tokens (access, refresh) are revoked
- API access is blocked
- Data is kept or archived according to the policy
Implementation
The table reads in columns: the left part is what the IdP triggers, the right
part is what the application must actually do for the promise to hold. The
N/A in row 2 is not a detail: SCIM has no operation to kill a session. If
the application does not cut its sessions by itself when it receives active: false, the person stays logged in until the cookie expires naturally.
| Step | IdP action | Application action |
|---|---|---|
| 1. Deactivation | active: false in the IdP | SCIM PATCH received |
| 2. Sessions | N/A | Invalidate all of the user's sessions |
| 3. Tokens | Revoke the refresh tokens | Delete the stored tokens |
| 4. Audit | Log the deprovisioning | Log the deactivation |
Data handling
What happens to the departed person's data is not decided case by case at the moment they leave: it is a policy written in advance, otherwise every offboarding becomes a negotiation. The three options below are rarely mutually exclusive, and are most often combined by type of data: keep the audit trails, anonymize personal data under the GDPR, delete personal drafts with no value.
| Option | When to use it |
|---|---|
| Keep | Compliance, history, audit |
| Anonymize | GDPR, personal data |
| Delete | Explicit request, non-critical data |
Common pitfalls
These five pitfalls all show up at the design of the integration, not in operation. They are therefore dealt with before going to production, in the order of the table: start by checking that the application really supports SCIM (many advertise "SSO" without provisioning), then validate the identifier mapping, and only then test the scenarios. The errors in the "Common mistakes" section, at the end of the page, appear instead once the system is in service.
| Pitfall | Consequence | Solution |
|---|---|---|
| The app does not support SCIM | Manual provisioning | Check before integrating |
externalId vs id mixed up | Updates fail | externalId = IdP, id = application |
| DELETE instead of disabling | Loss of history | Always prefer active: false |
| No session revocation | Access persists after departure | Implement revocation on the app side |
| Incorrect attribute mapping | Missing data | Test the mapping in pre-prod |
Keycloak + SCIM configuration
Since Keycloak 26.8.0 (October 1, 2026), the native SCIM API has moved
from preview to supported, and its feature is enabled by default on the
server. Keycloak plays the role of a SCIM server: an external system
(identity governance platform, HRIS, another IdP) creates, reads, modifies and
deletes the users and groups of a realm through the standard endpoints of RFC
7644, under /realms/<realm>/scim/v2, without going through the proprietary
administration REST API. You must also enable the "SCIM API" option realm by
realm (Realm settings, General tab) and present a bearer token holding the
required permissions. The step-by-step guide, with a departure handled from
end to end, is in Access review and lifecycle in Keycloak (in French).
This API covers the direction into Keycloak. The official documentation does not describe Keycloak as a SCIM client that would push its accounts to your applications: that second direction, the one in this page's diagram, remains to be verified version by version. Workflows, supported since 26.6, automate steps of the lifecycle (for example sending an invitation email when an account is created) without replacing a SCIM client. If you need to push accounts to third-party applications, a community extension like the one below remains an option, provided you check its compatibility before every major upgrade: you then entrust the lifecycle of your accounts to a third-party component whose update pace does not necessarily follow that of Keycloak.
With the scim-for-keycloak extension
Installation goes through Keycloak's provider mechanism: a JAR file dropped
into /opt/keycloak/providers/, which Keycloak then picks up by rebuilding its
startup image (the build). With kc.sh start, it runs this build implicitly
when it detects a new provider: a manual kc.sh build is only essential for
an optimized startup (--optimized), an option that assumes an already built
image and therefore does not load an extension added afterwards. On a Keycloak
deployed in a container, the practical consequence is that you must rebuild
the image or mount the JAR in a persistent volume, otherwise the extension
disappears at the next redeployment.
-
Install the extension
Terminal window # Add the SCIM provider to Keycloakcp scim-for-keycloak.jar /opt/keycloak/providers/# Explicit build: essential with --optimized, implicit with kc.sh start/opt/keycloak/bin/kc.sh build# Start on the built image (mandatory with --optimized)/opt/keycloak/bin/kc.sh start --optimized -
Configure the SCIM endpoint
In Keycloak Admin → Realm Settings → SCIM
-
Configure the client application
Enter the application's SCIM URL and the authentication token
-
Test the provisioning
Create a user in Keycloak and check that it is created in the application
Okta and Microsoft Entra ID (formerly Azure AD)
These IdPs integrate SCIM natively, which moves the difficulty: the protocol is no longer the issue, the attribute mapping becomes the issue. Step 4 is the one that takes the most time, because each application expects its fields under a name of its own. Going through the application catalog rather than a generic SCIM configuration spares you this work: the connector already ships with the mapping validated by the vendor.
- Add the application from the catalog
- Enable provisioning
- Enter the application's SCIM URL and token
- Map the attributes
- Test
SCIM vs other approaches
The selection criterion is not the technical quality of each approach, it is what the application on the other side can do. Read the last column first: it tells you which world you are in. Only one row should alert you, the one for JIT alone: with no deprovisioning, it guarantees orphaned accounts in the long run, and must therefore come with a manual periodic access review.
| Approach | Advantages | Drawbacks | Usage |
|---|---|---|---|
| SCIM | Standard, near real time, deprovisioning | The app must support it | Modern SaaS |
| JIT (SSO) | Simple, no configuration | No deprovisioning | Apps without SCIM |
| LDAP sync | Robust, mature | More complex, polling | On-prem apps |
| Custom API | Flexible | Tailor-made, maintenance | Legacy, special cases |
Common mistakes
Unlike the design pitfalls seen above, these four mistakes occur once the integration is in service, and they have one thing in common: they produce no visible error message. A SCIM provisioning that fails does so silently, on the IdP side, for an account that does not exist yet and that nobody is looking for. Hence the "No monitoring" row: without an alert on SCIM failures, you discover the problem on the day of an audit.
| Mistake | Consequence | Solution |
|---|---|---|
| No deprovisioning test | Orphaned accounts at the first departure | Test the full flow in pre-prod |
| SCIM token in clear text | Possible compromise | Store it in a vault |
| No monitoring | Silent failures | Alert on SCIM errors |
| Incomplete mapping | Missing attributes | Document and validate the mapping |