SCIM Provisioning
Automated user and group provisioning with SCIM 2.0 — connect Okta, Azure AD, or any compliant IdP to CoreSDK.
SCIM Provisioning
Phase note. This feature ships in Phase 2. The API shown is the planned surface.
CoreSDK will implement a SCIM 2.0 server endpoint (RFC 7643 / RFC 7644) that identity providers push to. When your IdP creates, updates, or deactivates a user, CoreSDK immediately reflects that change: tenant memberships are updated, active sessions are invalidated, and future token checks are rejected — with no polling and no manual sync.
How it works
- You enable the SCIM endpoint in CoreSDK and generate a bearer token for your IdP.
- You configure your IdP (Okta, Azure AD, etc.) to push SCIM events to
https://your-api.example.com/scim/v2. - CoreSDK processes incoming SCIM
POST,PUT,PATCH, andDELETErequests against its internal user store. - Group memberships from the IdP are mapped to CoreSDK roles and tenant assignments.
- Deprovisioned users have their sessions immediately invalidated; the next token check returns
401.
CoreSDK handles SCIM protocol details — schema negotiation, ETag concurrency, ListResponse pagination — so your application code only needs to react to higher-level lifecycle hooks.
Enabling the SCIM endpoint (Phase 2)
The SCIM endpoint is disabled by default. Enable it and set a secret bearer token that your IdP will include on every request.
use coresdk_engine::{Engine, EngineConfig, scim::ScimConfig};
use std::env;
let engine = Engine::new(EngineConfig {
tenant_id: "acme".to_string(),
scim: Some(ScimConfig {
enabled: true,
bearer_token: env::var("SCIM_BEARER_TOKEN")?,
base_path: "/scim/v2".to_string(),
allowed_ips: vec![],
}),
..Default::default()
})?;
// Mount the SCIM router alongside your existing routes.
let app = axum::Router::new()
.nest("/scim/v2", coresdk_engine::scim::scim_router(engine.clone()))
.route("/api/orders", axum::routing::get(orders_handler));import os
from coresdk import CoreSDKClient, SDKConfig
from coresdk.scim import ScimConfig
from fastapi import FastAPI
_sdk = CoreSDKClient(SDKConfig(
sidecar_addr="http://127.0.0.1:7233",
tenant_id="acme",
service_name="my-api",
scim=ScimConfig(
enabled=True,
bearer_token=os.environ["SCIM_BEARER_TOKEN"],
base_path="/scim/v2",
),
))
app = FastAPI()
app.mount("/scim/v2", _sdk.scim_app())// Phase 2 — Go SDK ships in Phase 2.
import (
"os"
sdk "github.com/coresdk-dev/sdk-go"
)
s := sdk.New(sdk.Config{
SidecarAddr: "http://127.0.0.1:7233",
TenantID: "acme",
SCIM: &sdk.SCIMConfig{
Enabled: true,
BearerToken: os.Getenv("SCIM_BEARER_TOKEN"),
BasePath: "/scim/v2",
},
})
mux := http.NewServeMux()
s.RegisterSCIMHandlers(mux)
mux.HandleFunc("/api/", yourHandler)// Phase 2 — TypeScript SDK ships in Phase 2.
import { CoreSDKClient } from "@coresdk/node";
import express from "express";
const sdk = new CoreSDKClient({
sidecarAddr: "http://127.0.0.1:7233",
tenantId: "acme",
scim: {
enabled: true,
bearerToken: process.env.SCIM_BEARER_TOKEN!,
basePath: "/scim/v2",
},
});
const app = express();
app.use("/scim/v2", sdk.scimRouter());Generating a bearer token
The bearer token is a shared secret between CoreSDK and your IdP. Generate a cryptographically random value and store it in your secrets manager:
# 32 bytes of entropy, base64-encoded
openssl rand -base64 32Never commit this value to source control. Reference it via an environment variable or your platform's secrets API (AWS Secrets Manager, GCP Secret Manager, Vault, etc.).
Supported SCIM resources
| Resource | Endpoint | Operations |
|---|---|---|
| Users | /scim/v2/Users | GET, POST, PUT, PATCH, DELETE |
| Groups | /scim/v2/Groups | GET, POST, PUT, PATCH, DELETE |
| ServiceProviderConfig | /scim/v2/ServiceProviderConfig | GET |
| ResourceTypes | /scim/v2/ResourceTypes | GET |
| Schemas | /scim/v2/Schemas | GET |
CoreSDK advertises its capabilities via ServiceProviderConfig so IdPs can auto-discover which optional SCIM features (filtering, sorting, bulk, ETags) are supported.
Configuring Okta
- In your Okta Admin Console, open Applications and select your app.
- Go to the Provisioning tab and click Configure API Integration.
- Check Enable API Integration and enter:
- Base URL:
https://your-api.example.com/scim/v2 - API Token: the bearer token you generated above
- Base URL:
- Click Test API Credentials — Okta will call
GET /scim/v2/Usersto verify connectivity. - Under Provisioning to App, enable the operations you want Okta to manage:
- Create Users — SCIM
POST /Users - Update User Attributes — SCIM
PUT /Users/{id} - Deactivate Users — SCIM
PATCH /Users/{id}withactive: false
- Create Users — SCIM
- Under Push Groups, add any Okta groups whose membership should be synced to CoreSDK.
- Save and assign the app to users or groups in the Assignments tab.
Okta attribute mapping
| Okta attribute | SCIM attribute | CoreSDK field |
|---|---|---|
| Login | userName | user.username |
emails[primary] | user.email | |
| First name | name.givenName | user.given_name |
| Last name | name.familyName | user.family_name |
| Active | active | user.active |
| Groups | groups[] | tenant memberships |
Configuring Azure AD (Entra ID)
- In the Azure portal, open Azure Active Directory > Enterprise applications and select your app (or create a new non-gallery app).
- Go to Provisioning and set Provisioning Mode to Automatic.
- Under Admin Credentials, enter:
- Tenant URL:
https://your-api.example.com/scim/v2 - Secret Token: the bearer token you generated above
- Tenant URL:
- Click Test Connection — Azure will call
GET /scim/v2/UsersandGET /scim/v2/Groups. - Expand Mappings to review attribute mappings for users and groups. The defaults work with CoreSDK out of the box.
- Under Settings, set Scope to Sync only assigned users and groups unless you want to sync your entire directory.
- Set Provisioning Status to On and save.
Azure AD attribute mapping
| Azure AD attribute | SCIM attribute | CoreSDK field |
|---|---|---|
| userPrincipalName | userName | user.username |
emails[primary] | user.email | |
| givenName | name.givenName | user.given_name |
| surname | name.familyName | user.family_name |
| accountEnabled | active | user.active |
| displayName | displayName | user.display_name |
User lifecycle
User deactivated
When an IdP sets active: false — either via a PATCH or by removing the user from the app assignment — CoreSDK:
- Marks the user as inactive in the user store.
- Immediately invalidates all active sessions for that user.
- Adds the user's
subto a short-lived rejection list so in-flight tokens are rejected on the next check, even before they expire.
PATCH /scim/v2/Users/00u1ab2cd3ef4gh56789
Authorization: Bearer <scim-bearer-token>
Content-Type: application/scim+json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "replace", "path": "active", "value": false }
]
}Group-to-role mapping (Phase 2)
SCIM groups from your IdP are mapped to CoreSDK roles and tenant memberships via a mapping table in your configuration.
use coresdk_engine::scim::{ScimConfig, GroupMapping};
ScimConfig {
enabled: true,
bearer_token: std::env::var("SCIM_BEARER_TOKEN")?,
base_path: "/scim/v2".to_string(),
group_mappings: vec![
GroupMapping { group_name: "acme-admins".to_string(), role: "admin".to_string(), tenant: None },
GroupMapping { group_name: "acme-editors".to_string(), role: "editor".to_string(), tenant: None },
GroupMapping { group_name: "acme-eu-users".to_string(), role: "member".to_string(), tenant: Some("acme-eu".to_string()) },
],
..Default::default()
}from coresdk.scim import ScimConfig, GroupMapping
ScimConfig(
enabled=True,
bearer_token=os.environ["SCIM_BEARER_TOKEN"],
base_path="/scim/v2",
group_mappings=[
GroupMapping(group_name="acme-admins", role="admin"),
GroupMapping(group_name="acme-editors", role="editor"),
GroupMapping(group_name="acme-eu-users", role="member", tenant="acme-eu"),
],
)// Phase 2 — Go SDK ships in Phase 2.
GroupMappings: []sdk.GroupMapping{
{GroupName: "acme-admins", Role: "admin"},
{GroupName: "acme-editors", Role: "editor"},
{GroupName: "acme-eu-users", Role: "member", Tenant: "acme-eu"},
},// Phase 2 — TypeScript SDK ships in Phase 2.
groupMappings: [
{ groupName: "acme-admins", role: "admin" },
{ groupName: "acme-editors", role: "editor" },
{ groupName: "acme-eu-users", role: "member", tenant: "acme-eu" },
],Verifying the integration
CoreSDK ships with a CLI tool that simulates IdP SCIM pushes so you can test without configuring a real IdP:
# Create a test user
coresdk scim test --endpoint http://localhost:8080/scim/v2 \
--token $SCIM_BEARER_TOKEN \
user create --email alice@example.com --name "Alice Smith"
# Deactivate Alice and verify her sessions are gone
coresdk scim test --endpoint http://localhost:8080/scim/v2 \
--token $SCIM_BEARER_TOKEN \
user deactivate --email alice@example.comNext steps
- Sessions — how CoreSDK manages session state and how SCIM deprovisioning invalidates sessions (Phase 2)
- Multi-tenancy: Provisioning — automated tenant creation tied to SCIM group events
- Observability: Audit Log — structured audit events for every SCIM operation (Phase 2)