Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français
Lire en français

SCIM: automatic identity provisioning

21 min read
Improve this page

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:

  1. An employee joins → manual account creation in every application
  2. The employee changes department → manual update of the groups
  3. 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:

  1. The user logs in for the first time
  2. The application receives the SAML assertion or the ID Token
  3. 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:

  1. A user is created/modified/disabled in the IdP
  2. The IdP sends a SCIM request to the application
  3. 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.

SCIM architecture: the Identity Provider (Keycloak, Okta, Azure AD), acting as the SCIM Client, pushes accounts to the application (Salesforce, Slack, Jira), acting as the SCIM Server, through the SCIM API with POST, PUT, PATCH and DELETE.SCIM APIPOST, PUT, PATCH, DELETEIdentity Provider (SCIM Client)Keycloak, Okta, Azure ADApplication (SCIM Server)Salesforce, Slack, Jira
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

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.

AttributeDescription
idUnique identifier on the application side
externalIdUnique identifier on the IdP side
userNameLogin identifier (often the email)
activeAccount enabled or disabled
emailsList of email addresses
groupsGroups 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.

OperationHTTP methodEndpointUsage
CreatePOST/UsersNew employee
ReadGET/Users/{id}Check an account
ListGET/Users?filter=...Search for users
UpdatePUT/Users/{id}Full replacement
Partial updatePATCH/Users/{id}Targeted change
DeleteDELETE/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:

Terminal window
POST /scim/v2/Users
Content-Type: application/scim+json
Authorization: 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):

Terminal window
PATCH /scim/v2/Users/2819c223-7f76-453a-919d-413861904646
Content-Type: application/scim+json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "active",
"value": false
}
]
}

Search for users:

Terminal window
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.

  1. Creation in the source system

    The HR department records the new hire in its reference tool (Workday, Active Directory...).

  2. Synchronization to the IdP

    The central IdP (Keycloak, Okta) receives the user.

  3. SCIM provisioning

    The IdP pushes the user to the configured applications.

  4. Confirmation

    Each application returns a SCIM id that 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.

  1. Group change in the IdP

    The user changes team, their groups are updated.

  2. SCIM PATCH to the applications

    The IdP sends the group changes: add the new one, remove the old one.

  3. 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.

  1. Deactivation in the IdP

    The account is marked active: false in the IdP, which refuses any new authentication from that moment on.

  2. SCIM PATCH: disable

    The IdP sends { "active": false } to all the applications.

  3. Token revocation

    The applications invalidate existing sessions and tokens.

  4. 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.

  1. The account is disabled (no new logins)
  2. Active sessions are terminated
  3. Tokens (access, refresh) are revoked
  4. API access is blocked
  5. 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.

StepIdP actionApplication action
1. Deactivationactive: false in the IdPSCIM PATCH received
2. SessionsN/AInvalidate all of the user's sessions
3. TokensRevoke the refresh tokensDelete the stored tokens
4. AuditLog the deprovisioningLog 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.

OptionWhen to use it
KeepCompliance, history, audit
AnonymizeGDPR, personal data
DeleteExplicit 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.

PitfallConsequenceSolution
The app does not support SCIMManual provisioningCheck before integrating
externalId vs id mixed upUpdates failexternalId = IdP, id = application
DELETE instead of disablingLoss of historyAlways prefer active: false
No session revocationAccess persists after departureImplement revocation on the app side
Incorrect attribute mappingMissing dataTest 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.

  1. Install the extension

    Terminal window
    # Add the SCIM provider to Keycloak
    cp 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
  2. Configure the SCIM endpoint

    In Keycloak Admin → Realm Settings → SCIM

  3. Configure the client application

    Enter the application's SCIM URL and the authentication token

  4. 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.

  1. Add the application from the catalog
  2. Enable provisioning
  3. Enter the application's SCIM URL and token
  4. Map the attributes
  5. 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.

ApproachAdvantagesDrawbacksUsage
SCIMStandard, near real time, deprovisioningThe app must support itModern SaaS
JIT (SSO)Simple, no configurationNo deprovisioningApps without SCIM
LDAP syncRobust, matureMore complex, pollingOn-prem apps
Custom APIFlexibleTailor-made, maintenanceLegacy, 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.

MistakeConsequenceSolution
No deprovisioning testOrphaned accounts at the first departureTest the full flow in pre-prod
SCIM token in clear textPossible compromiseStore it in a vault
No monitoringSilent failuresAlert on SCIM errors
Incomplete mappingMissing attributesDocument and validate the mapping

Key takeaways

References

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn