Skip to main content
CoreSDK
Authentication

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

  1. You enable the SCIM endpoint in CoreSDK and generate a bearer token for your IdP.
  2. You configure your IdP (Okta, Azure AD, etc.) to push SCIM events to https://your-api.example.com/scim/v2.
  3. CoreSDK processes incoming SCIM POST, PUT, PATCH, and DELETE requests against its internal user store.
  4. Group memberships from the IdP are mapped to CoreSDK roles and tenant assignments.
  5. 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 32

Never 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

ResourceEndpointOperations
Users/scim/v2/UsersGET, POST, PUT, PATCH, DELETE
Groups/scim/v2/GroupsGET, POST, PUT, PATCH, DELETE
ServiceProviderConfig/scim/v2/ServiceProviderConfigGET
ResourceTypes/scim/v2/ResourceTypesGET
Schemas/scim/v2/SchemasGET

CoreSDK advertises its capabilities via ServiceProviderConfig so IdPs can auto-discover which optional SCIM features (filtering, sorting, bulk, ETags) are supported.

Configuring Okta

  1. In your Okta Admin Console, open Applications and select your app.
  2. Go to the Provisioning tab and click Configure API Integration.
  3. Check Enable API Integration and enter:
    • Base URL: https://your-api.example.com/scim/v2
    • API Token: the bearer token you generated above
  4. Click Test API Credentials — Okta will call GET /scim/v2/Users to verify connectivity.
  5. 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} with active: false
  6. Under Push Groups, add any Okta groups whose membership should be synced to CoreSDK.
  7. Save and assign the app to users or groups in the Assignments tab.

Okta attribute mapping

Okta attributeSCIM attributeCoreSDK field
LoginuserNameuser.username
Emailemails[primary]user.email
First namename.givenNameuser.given_name
Last namename.familyNameuser.family_name
Activeactiveuser.active
Groupsgroups[]tenant memberships

Configuring Azure AD (Entra ID)

  1. In the Azure portal, open Azure Active Directory > Enterprise applications and select your app (or create a new non-gallery app).
  2. Go to Provisioning and set Provisioning Mode to Automatic.
  3. Under Admin Credentials, enter:
    • Tenant URL: https://your-api.example.com/scim/v2
    • Secret Token: the bearer token you generated above
  4. Click Test Connection — Azure will call GET /scim/v2/Users and GET /scim/v2/Groups.
  5. Expand Mappings to review attribute mappings for users and groups. The defaults work with CoreSDK out of the box.
  6. Under Settings, set Scope to Sync only assigned users and groups unless you want to sync your entire directory.
  7. Set Provisioning Status to On and save.

Azure AD attribute mapping

Azure AD attributeSCIM attributeCoreSDK field
userPrincipalNameuserNameuser.username
mailemails[primary]user.email
givenNamename.givenNameuser.given_name
surnamename.familyNameuser.family_name
accountEnabledactiveuser.active
displayNamedisplayNameuser.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:

  1. Marks the user as inactive in the user store.
  2. Immediately invalidates all active sessions for that user.
  3. Adds the user's sub to 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.com

Next steps

On this page