Automate User Provisioning with SCIM Directory Sync
Learn how to automate user provisioning and deprovisioning for your AI skills registry with SCIM 2.0. Step-by-step setup guides for Okta and Azure AD included.
What is SCIM and why does it matter for developer tools?
SCIM (System for Cross-domain Identity Management) is an open standard (RFC 7643 and RFC 7644) that lets your identity provider automatically create, update, and delete user accounts in connected applications. When a developer joins your company and gets added to the right groups in Okta or Azure AD, SCIM provisions their access to every connected tool automatically. When they leave, deprovisioning happens the same way: one action in your IdP removes access everywhere.
For developer tools like AI coding assistants and skills registries, this matters more than it might seem. Without SCIM, onboarding a new engineer means someone manually adding them to GitHub, Slack, Linear, your internal skills registry, and a dozen other tools. Offboarding is worse - accounts get missed, and former employees retain access to sensitive code and tooling longer than they should.
If you're running SSO with SAML, SCIM is the natural next step. SAML handles authentication (who can log in), while SCIM handles provisioning (who has an account at all). Together, they give you full lifecycle control over developer tool access.
How SCIM 2.0 provisioning works
SCIM works through a simple REST API that your identity provider calls on behalf of your application. The flow looks like this:
- An admin adds a user to a group in Okta (or Azure AD, Google Workspace, etc.)
- The IdP sends a POST request to your SCIM endpoint to create the user
- Your application creates the account and returns the user record
- When the user is removed from the group, the IdP sends a DELETE or PATCH (deactivate) request
- Your application removes or suspends the account
The SCIM spec defines two core resource types: Users and Groups. Users map to individual accounts. Groups map to teams or roles - so you can say "everyone in the engineering-platform group in Okta gets the admin role in your skills registry."
SCIM attribute mapping
Most IdPs let you configure which SCIM attributes map to which fields in your application. The standard attributes are:
| SCIM Attribute | Typical Use |
|---|---|
userName | Unique identifier, usually email |
name.givenName | First name |
name.familyName | Last name |
emails[primary] | Primary email address |
active | Account enabled/disabled |
groups | Group memberships |
externalId | IdP-side unique ID |
The externalId field is important - it's the IdP's internal user ID, which stays stable even if the user changes their email. Always store and index this field; it's how you reconcile updates reliably.
Setting up SCIM with Okta
Okta is the most common enterprise IdP. Here's the step-by-step process for configuring SCIM provisioning.
Step 1: Create a SCIM provisioning token
In localskills.sh, navigate to Settings > Security > SCIM. Generate a new SCIM provisioning token. This is a long-lived bearer token your IdP will use to authenticate requests to the SCIM endpoint.
Store this token securely - you'll only see it once. If it's compromised, you can rotate it from the same settings page without disrupting existing provisioned users.
Note: SCIM tokens are separate from team API tokens and have a different permission scope - they can only manage users and groups, not read skill content or modify packages.
Step 2: Configure the Okta SCIM application
In your Okta admin console:
- Go to Applications > Applications > Browse App Catalog
- Search for your application or use the SWA (Secure Web Authentication) app type for custom SCIM endpoints
- Under the Provisioning tab, enable SCIM provisioning
- Set the SCIM connector base URL to
https://localskills.sh/api/scim/v2 - Set Unique identifier field for users to
userName - Set Authentication Mode to HTTP Header, and paste your provisioning token in the Authorization field as
Bearer <your-token>
Step 3: Enable provisioning actions
Still on the Provisioning tab, under To App, enable:
- Create Users - provisions new accounts when users are assigned
- Update User Attributes - keeps names and emails in sync
- Deactivate Users - disables accounts when users are unassigned (recommended over Delete)
Use Deactivate, not Delete. Deactivation preserves the user's contribution history and makes it easy to reinstate access if someone returns. Hard deletion is irreversible.
Step 4: Assign groups
Under the Assignments tab, assign the Okta groups whose members should be provisioned. Typically this is an engineering group or a tool-specific group like localskills-users.
Okta will immediately start provisioning existing group members and will keep the roster in sync as people join or leave.
Setting up SCIM with Azure Active Directory
Azure AD (now called Microsoft Entra ID) uses the same SCIM 2.0 standard but has a slightly different setup flow.
Enterprise application configuration
- In the Azure portal, go to Azure Active Directory > Enterprise applications > New application
- Choose Create your own application and select Non-gallery application
- Give it a name (e.g., "localskills") and click Create
- Under Provisioning, set the Provisioning Mode to Automatic
- In Admin Credentials, set the Tenant URL to
https://localskills.sh/api/scim/v2and paste your provisioning token in the Secret Token field - Click Test Connection to verify
Attribute mapping in Azure AD
Azure AD has sensible defaults, but verify the externalId mapping points to the Azure AD objectId field, not userPrincipalName. The object ID is stable; the UPN can change when someone changes their email.
{
"externalId": "objectId",
"userName": "userPrincipalName",
"active": "accountEnabled",
"emails[type eq \"work\"].value": "mail",
"name.givenName": "givenName",
"name.familyName": "surname"
}
Group-to-role mapping
SCIM provisions users, but you also want group membership to control what those users can do. SCIM Groups map to roles in localskills.sh:
| IdP Group Name | localskills.sh Role |
|---|---|
localskills-admins | Organization admin |
localskills-publishers | Can publish skills |
localskills-members | Read-only, can install |
You configure this mapping under Settings > Security > SCIM > Group Mappings. You specify the SCIM group displayName (which matches your IdP group name) and the target role.
When a user belongs to multiple mapped groups, they get the highest-privilege role. A user in both localskills-members and localskills-publishers gets the publisher role.
Dynamic role updates
Role changes propagate automatically. If you promote an engineer from member to publisher in your IdP by moving them between groups, SCIM sends a PATCH request that updates their role in localskills.sh within seconds - no manual action required.
This tight integration becomes important when you're enforcing audit logging and compliance requirements. Every role change made through SCIM is logged with the IdP-sourced event as context, giving you a complete chain of custody for access changes.
Managing the SCIM token lifecycle
SCIM tokens are long-lived credentials. Unlike OAuth access tokens that expire in hours, a SCIM token typically lasts until you rotate it. This makes rotation hygiene important.
Best practices for SCIM token management
Rotate tokens on a schedule. Quarterly rotation is a reasonable baseline. Rotate immediately if the token is exposed (e.g., accidentally committed to a repository).
Use a dedicated service account. Don't tie the SCIM token to an individual user's account. Create a machine account in your IdP dedicated to provisioning, and generate the token under that account. When the person who set up the integration leaves the company, their departure shouldn't break SCIM.
Scope the token minimally. SCIM tokens in localskills.sh can only call the /api/scim/v2 endpoints. They cannot read skill content, modify packages, or access analytics. This limits blast radius if a token is compromised.
Monitor for unexpected calls. The SCIM audit log (under Settings > Logs > SCIM Events) shows every provisioning action. Unexpected user creations or deletions from unknown IP ranges are worth investigating.
Rotating a SCIM token without downtime
- Generate a new token in localskills.sh Settings - the old token remains active
- Update the token in your IdP (Okta: Applications > Provisioning > Edit; Azure AD: Enterprise App > Provisioning > Admin Credentials)
- Trigger a test sync from the IdP to verify the new token works
- Revoke the old token in localskills.sh
This two-token overlap ensures provisioning doesn't drop while you update the IdP config.
Troubleshooting common SCIM issues
Users provisioned but not receiving expected roles
Check your group mappings under Settings > Security > SCIM > Group Mappings. The group displayName in the mapping must exactly match what the IdP sends in the SCIM payload - including case. "Engineering" and "engineering" are different values.
Inspect the raw SCIM payload in the event log. The groups array in the provisioning request shows exactly what group memberships your IdP is sending.
Provisioning succeeds but SSO login fails
SCIM and SAML are independent. A successful SCIM provision just means the account exists in localskills.sh. If SSO login fails afterward, check that the userName (email) sent by SCIM matches the NameID in your SAML assertion. Mismatched email formats (firstname.lastname@company.com vs flastname@company.com) cause this consistently. See the SSO setup guide for SAML debugging steps.
Deprovisioned users still appearing as active
Some IdPs send a PATCH with "active": false instead of a DELETE. Verify your Deactivate Users provisioning action is enabled in the IdP config. If you only enabled Delete Users, deactivation requests will succeed at the HTTP level but the account won't be disabled in localskills.sh.
Sync errors: 409 Conflict
A 409 conflict means the IdP is trying to create a user who already exists but is identified by a different externalId. This happens when you migrate IdPs or when someone already had a localskills.sh account created before SCIM was set up.
Resolve it by linking the existing account to the IdP user: in localskills.sh, go to Settings > Members, find the user, and set their externalId to match the IdP value. Subsequent provisioning updates will then reconcile correctly.
Rate limiting
The SCIM API enforces rate limits per token. Initial bulk provisioning of a large organization (500+ users) may hit these limits. Most IdPs handle rate-limit responses (HTTP 429) with automatic backoff and retry. If you're seeing slow initial sync, this is expected - Okta and Azure AD will work through the backlog within an hour.
Putting it all together: enterprise-ready AI tooling
SCIM provisioning is one piece of an enterprise security posture for AI developer tools. The full picture looks like:
- SSO with SAML - engineers authenticate with your corporate identity, not a separate password
- SCIM directory sync - user lifecycle is automated, tied to your HR and IT systems
- Role-based access control - groups in your IdP control what each engineer can publish or install
- Audit logging - every install, publish, and access change is logged for compliance review
- Team skill management - standardized AI conventions are enforced through the registry, not ad-hoc file sharing
When you automate provisioning, you remove a whole class of security risk. The most common cause of ex-employee data exposure isn't a sophisticated breach - it's an offboarding checklist item that got missed. SCIM makes deprovisioning automatic and immediate.
Ready to set up SCIM provisioning for your team? Enterprise features including SCIM, SAML SSO, and audit logs are available on the Teams and Enterprise plans. Create your organization and invite your team today.
npm install -g @localskills/cli
localskills login
localskills org create