How SCIM provisioning works - tutorial with API calls
SCIM is a widely used protocol, but not many people understand it. This straightforward and comprehensive guide steps through how it works, using real-world examples and API calls and responses.
How SCIM provisioning works - tutorial with API calls
Your startup is growing like crazy, adding new team members weekly and using an Identity Provider (IdP) to manage user access. Without SCIM, your IT team must manually create accounts for every new hire. That's time-consuming and error-prone.
System for Cross-domain Identity Management (SCIM) automates adding users to the set of applications your company uses so that new hires can be productive immediately.
In this tutorial, we'll step through the full SCIM lifecycle, showing API calls and responses between an Identity Provider (IdP) and a Service Provider. You will learn:
- How SCIM leverages technologies you already know, such as REST endpoints, and token-based authentication
- User CRUD with SCIM: what it looks like to create (provision), update, or delete (deprovision) users
- SCIM protocol fundamentals: users, groups, synchronization, and service provider configurations
A practical SCIM example: provisioning new users in GitHub
When your startup hires a new engineer and adds them to the company IdP, they should also get a fresh GitHub account. Next, they’ll need to be added to the right GitHub team so they can see the right codebases and start collaborating with the rest of engineering.
Let’s step through how this works end to end:
- Authentication: How the Service Provider (GitHub) knows to trust requests from your IdP
- Provisioning: Adding a new user
- Modifying: Users to add them to the correct team
- Synchronization: Checking for out-of-band changes to the user
- Deprovisioning: Removing the user when they leave your company
- Handling errors and rate limits: Making your SCIM calls robust to failure
- Service Provider Configuration: How your code can figure out what features and limits a given Service Provider supports
1. Authentication: How does the service provider know to trust your IdP’s requests?
What prevents someone outside your company from making a call to GitHub’s SCIM endpoint to provision a user for themselves, which would be a serious security issue?
SCIM endpoints use OAuth 2.0 Bearer tokens for authentication. While JSON Web Tokens (JWTs) are commonly used as Bearer tokens, other token formats are also supported by the OAuth 2.0 specification. The token is supplied in the Authorization header of the HTTP request when calling a Service Provider like GitHub.
The Service Provider validates tokens by verifying their digital signatures using the IdP's public keys (JWKs - JSON Web Key Sets). This cryptographic validation ensures that only tokens issued by your authorized IdP are accepted.
Here's an example of making a call to create a new user, passing the IdP-issued token in the Authorization header:
The sample code above calls your service provider’s SCIM API’s Users endpoint to provision a new user:
`https://api.service-provider.com/scim/v2/Users`
SCIM provides standardized REST endpoints for operations on users and groups:
/Users
: Manages user resources (create, read, update, delete users)/Groups
: Manages team memberships/ServiceProviderConfig
: Exposes supported features and configurations
Now that we’ve got the basics of authentication and endpoints down, let’s see both in action as we provision users for your newest team member, John Doe.
2. Provisioning: creating a new user in your application automatically
When you assign an application to a new hire “john.doe@example.com" in your IdP, this API call is automatically made:
Note the externalId
field in our request - this is a crucial SCIM concept that helps correlate identities between systems.
Your IdP typically sets this to its internal user identifier, allowing it to maintain a stable reference to the user even if other attributes like email change. Think of it as a foreign key linking the user's identity across your systems.
In response to this call, the Service Provider confirms the user creation:
Now John Doe has a new GitHub account, but we still need to assign them to the correct team. Note that the service provider returns the internal ID it uses to represent this user.
3. Modification: updating the user to add them to the correct team
Group membership can be managed either through PATCH operations on the user or directly via the /Groups endpoint, depending on your service provider's implementation. Here's an example using PATCH:
The Service Provider confirms the team assignment:
At this point, John Doe has been successfully provisioned and added to the correct team, so he can get to work!
4. Synchronization: checking for out-of-band changes
What if you make changes to a user that was provisioned via SCIM, perhaps by updating your GitHub account settings? How does your IdP keep everything in sync?
Your IdP needs to track both creation and modification timestamps to maintain proper synchronization. Here's how it might query for updates:
For initial synchronization, you might also need to query based on creation date:
Webhooks are another common solution to synchronization challenges - they allow your Service Providers to send critical events like user updates to endpoints you control so that you can update records within your IdP and other systems.
Like everything in engineering, this approach comes with tradeoffs - a potentially more scalable solution is to use an events API to be notified of critical SCIM updates.
5. Deprovisioning: removing a user when they leave the company
It turns out that John Doe doesn’t value code quality or security.
When an employee leaves, the deprovisioning behavior varies by service provider. Some providers deactivate accounts, while others support permanent deletion.
Here's an example of deactivation:
For providers that support deletion, the call may look something like this:
That’s the full SCIM lifecycle for a user completed, but what other concerns must we address when building a robust and performant SCIM implementation?
Most of the same issues that affect any other REST API: handling failures, timeouts, rate limits and preventing duplicate entities. We’ll examine how to tackle these next.
6. Handling errors, rate limits and idempotency: making your SCIM calls robust to failure
SCIM operations can fail for various reasons, including:
- Network connectivity issues
- Invalid tokens, expired tokens or authentication failures (HTTP 401)
- Rate limiting (HTTP 429)
- Server errors (HTTP 5xx)
Here's an example of implementing exponential backoff for handling rate limits:
Timeouts and rate limits are one problem, but what if you’re making a lot of requests very quickly - how can you avoid duplicate account creation issues?
One approach is using idempotency tokens which are unique to every request. It’s common to use a UUID library or function to implement this:
Another approach to preventing conflicting updates is using ETags and conditional headers:
Robust SCIM implementations should include:
- Retry logic with exponential backoff
- Administrator notifications for persistent failures
- Logging for audit trails and troubleshooting
- Handling of concurrent provisioning requests, ensuring no duplicate entities
Another challenge of implementing SCIM properly is managing the many different Service Providers’ quirks and capabilities. One application may allow bulk operations, another may not. One application might support filtering up to 100 results at once, another may have a much higher limit.
Aside from documentation, how can we ensure we’re building against the true limits and functionality of a given Service Provider? The third standard SCIM endpoint, `/ServiceProviderConfig`
, exposes dynamic information specific to a given provider.
6. Service provider configuration: dynamically determine provider capabilities
The ServiceProviderConfig endpoint tells you what SCIM features a service supports. Here's an example response. Take a look and consider what kind of things is this information useful for:
The ServiceProviderConfig endpoint helps during development and runtime. You can verify support features during implementation.
You can also use this endpoint to enable or disable functionality based on service provider capabilities or adapt to hard limits, such as the number of bulk operations the target application will perform at once.
Since these Service Provider configuration responses rarely change, you can implement caching to reduce the number of total API calls you need to make.
Ready to add SCIM to your application?
We've demonstrated the basic SCIM flow end to end here, but implementing SCIM in your production application requires building and maintaining complex provisioning logic, user synchronization, and enterprise-grade security features.
WorkOS already built these components so that you can make your app Enterprise Ready in minutes, not months. Our SCIM implementation provides:
- Automatic retry handling
- Support for all major SCIM versions (1.1, 2.0)
- Built-in compatibility with all major identity providers
- Provider-specific mappings and transformations
- Comprehensive audit logging
- Enterprise-grade security and compliance
When enterprises ask if your app supports automated user provisioning, you can say yes—without spending months building SCIM yourself. Try WorkOS today, and start selling to enterprise customers tomorrow.