Sign in →
Governance1 min read

SCIM Provisioning

Auto-provision and deprovision storefront portal users from Okta, Azure AD, OneLogin, or JumpCloud over SCIM 2.0. Mint a bearer token, point your IdP at one URL, and let your directory of record drive access.

Updated 2026-06-24Suggest edits
Docs Governance SCIM Provisioning

Overview#

When someone joins, moves teams, or leaves your company, your identity provider already knows. SCIM provisioning makes Aforo follow that directory of record automatically — a new hire gets a storefront portal account the moment your IdP assigns them the Aforo app, and a departing employee loses access the moment they're deprovisioned. No manual user creation, no orphaned accounts after offboarding.

Aforo implements SCIM 2.0 (RFC 7643 / 7644) for storefront portal users. You mint a bearer token in the operator console, paste one endpoint URL into your IdP's provisioning settings, and the IdP pushes create / update / deactivate calls to Aforo over the standard protocol. It works with any SCIM-compliant IdP; this guide covers the four most common — Okta, Azure AD (Entra ID), OneLogin, and JumpCloud.

INFO
Where this runs. The SCIM endpoints live in storefront-service and are reached directly at your tenant's storefront host — https://{tenant}.storefront.aforo.ai/scim/v2/ — not through the API gateway. SCIM is an IdP-to-backend channel, so it isn't part of the customer-facing Kong surface. The {tenant} segment is your tenant slug; the operator console shows your exact URL with a copy button (Governance → SCIM Provisioning).

How provisioning works#

Three moving parts, set up once:

  1. A SCIM bearer token you mint in Aforo. Your IdP sends it asAuthorization: Bearer … on every request. Aforo stores only a SHA-256 hash of it — the plaintext is shown once.
  2. The SCIM endpoint URL you paste into your IdP. Every user lifecycle event your IdP emits becomes a SCIM call to /scim/v2/Users.
  3. The mapping. Your IdP sends each user's stable directory ID as externalId and their email as userName. Aforo keys on externalId so the same person is never double-created, even if their email changes.

Once connected, the IdP owns the lifecycle: it POSTs new users, PATCHes profile changes, and DELETEs (deactivates) offboarded users on its own schedule. You don't call these endpoints by hand — but you can, with curl, to verify the connection (see Verify the connection).

Step 1 — Mint a SCIM token#

In the operator console, go to Governance → SCIM Provisioning and mint a token (name it after the IdP it's for, e.g. Okta Production). The console calls:

Mint a SCIM token (operator console does this for you)
curl -X POST https://storefront.aforo.ai/api/v1/storefront/scim/tokens \
  -H "Authorization: Bearer <YOUR_OPERATOR_JWT>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Okta Production" }'

# 201 Created
{
  "success": true,
  "data": {
    "id": "a1b2c3d4-…",
    "name": "Okta Production",
    "prefix": "aforo_scim_",
    "plaintext": "aforo_scim_Xk9vG2pL5mZ8nQ1w6jH3",   // shown ONCE
    "createdAt": "2026-06-24T14:30:00Z"
  }
}
WARNING
The plaintext token is shown once. Aforo stores only its SHA-256 hash, so it can never be recovered — the console also auto-clears it from the screen after 10 minutes. Copy it straight into your IdP. If you lose it, mint a new one and revoke the old. Revoke is irreversible: the IdP starts failing on its next sync, which is exactly what you want when a token leaks.

Minting and revoking tokens requires the OWNER or ADMIN role. List your tokens (names, prefixes, last-used, revoked state — never the plaintext) with GET /api/v1/storefront/scim/tokens, and revoke with DELETE /api/v1/storefront/scim/tokens/{id}.

Step 2 — Configure your IdP#

Every IdP asks for the same two things: the SCIM base URL and the bearer token. The values to paste are identical across providers — only the menu path differs. Pick your IdP:

Okta Admin → Applications → your Aforo app → Provisioning → Configure API Integration

  ✓ Enable API integration
  SCIM connector base URL:  https://{tenant}.storefront.aforo.ai/scim/v2/
  Unique identifier field for users:  email
  Authentication Mode:  HTTP Header
  Authorization:  Bearer aforo_scim_…   (the token you minted)

Test API Credentials → Save.
Then under "To App", enable: Create Users, Update User Attributes, Deactivate Users.

Replace {tenant} with your tenant slug (copy the exact URL from the SCIM Provisioning page). After the IdP's "Test Connection" passes, assign users or groups to the Aforo app in your IdP — that's what triggers the first provisioning sync.

What each SCIM operation does#

You don't write this code — your IdP does — but knowing how Aforo interprets each call is the difference between "why is this user still active?" taking five seconds or an afternoon.

IdP actionSCIM callWhat Aforo does
Assign user to appPOST /UsersCreates a storefront portal user. Idempotent — keyed on externalId, then email. A retry of the same user returns 200 with the existing record, never a duplicate.
Edit profilePATCH /Users/{id}Applies the Operations array — replace/add/remove on active, userName, name.givenName, name.familyName, externalId.
Replace whole recordPUT /Users/{id}Full overwrite. Honors If-Match (see ETags below).
Unassign / offboardDELETE /Users/{id}Soft-deactivates — sets the user inactive (active: false) and returns 204. The record is kept for audit and historical references; a true erase is a separate portal grace-period flow.
Reconcile / searchGET /Users?filter=…Lists users. Fast-path userName eq "x@y.com"; full filter supports eq ne co sw ew pr joined with and only. Paginated (startIndex / count, default 50, max 1000).

Optimistic concurrency (ETags)#

Every user carries a weak ETag in meta.version (W/"…") derived from its last-modified time. An IdP that sends If-Match on PUT/PATCH/DELETE gets a 412 Precondition Failed if the record changed underneath it — re-GET the user for the current version and retry. ETags are optional; IdPs that don't send If-Match get last-writer-wins.

Verify the connection#

Before you trust the IdP's "Test Connection" button, hit the endpoints yourself. Export your token once, then run these in order. Note the base URL ends in /scim/v2 and the token authenticates every call — including the discovery endpoints.

Smoke-test the SCIM connection
export SCIM=https://acme.storefront.aforo.ai/scim/v2
export TOKEN=aforo_scim_Xk9vG2pL5mZ8nQ1w6jH3

# 1. Discovery — confirms auth + advertises capabilities
curl -s "$SCIM/ServiceProviderConfig" -H "Authorization: Bearer $TOKEN" | jq .

# 2. Create a user (idempotent on externalId)
curl -s -X POST "$SCIM/Users" -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/scim+json" -d '{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
    "externalId": "okta-00u123",
    "userName": "ada@acme.com",
    "name": { "givenName": "Ada", "familyName": "Lovelace" },
    "emails": [{ "value": "ada@acme.com", "primary": true, "type": "work" }],
    "active": true
  }' | jq '.id, .active, .meta.version'

# 3. Find them again
curl -s "$SCIM/Users?filter=userName%20eq%20%22ada@acme.com%22" \
  -H "Authorization: Bearer $TOKEN" | jq '.totalResults'

# 4. Deactivate (offboard) — soft-deactivate, returns 204
curl -s -o /dev/null -w "%{http_code}\n" -X DELETE "$SCIM/Users/<id>" \
  -H "Authorization: Bearer $TOKEN"

A clean run is: 200 on discovery, an id + active: true on create, a non-zero totalResults on the filter, and 204 on delete. If any step returns 401, your token is wrong, missing, or revoked — see Troubleshooting.

Groups#

Aforo accepts group provisioning at /scim/v2/Groups — create, rename, add/remove members, delete — so your IdP can push group membership and you can audit it (the SCIM Provisioning page shows synced groups with their members). This is useful for visibility: you can see exactly which groups each token has provisioned.

WARNING
Groups are an audit mirror in v1, not an authorization source. Aforo does not yet consult SCIM groups to grant entitlements or portal permissions — group-to-role / group-to-team mapping is a planned follow-up. Assign what a customer can do through offerings and subscriptions, not through SCIM group membership. Pushing groups today is safe and lossless; just don't expect them to drive access until that mapping ships.

API reference#

The full SCIM surface — every endpoint, request schema, and response shape — is in the interactive API reference under the Authentication family. The endpoints at a glance:

Method & pathAuthWhat it does
GET /scim/v2/ServiceProviderConfigSCIM tokenCapability discovery (patch ✓, filter ✓, sort ✓, etag ✓, bulk ✕, changePassword ✕)
GET /scim/v2/Schemas · /ResourceTypesSCIM tokenUser + Group schema and resource-type definitions
GET /scim/v2/UsersSCIM tokenList / filter / sort / paginate users
POST /scim/v2/UsersSCIM tokenCreate user (idempotent on externalId → email)
GET·PUT·PATCH·DELETE /scim/v2/Users/{id}SCIM tokenFetch / replace / patch / soft-deactivate a user
GET·POST /scim/v2/Groups · …/{id}SCIM tokenGroup CRUD + membership (audit mirror, v1)
POST·GET·DELETE /api/v1/storefront/scim/tokensOWNER / ADMINMint (plaintext once) / list / revoke SCIM tokens
GET /api/v1/storefront/scim/configOWNER / ADMINYour tenant SCIM endpoint URL + auth scheme

What SCIM does not do#

The honest edges — knowing these up front saves a support ticket:

  • Groups don't grant access (v1). They're stored and auditable but not yet wired to authorization. Entitlements come from offerings/subscriptions.
  • No password sync. changePassword is false — the storefront portal owns password reset. SCIM never carries credentials.
  • No bulk endpoint. bulk is false; IdPs fall back to per-user requests automatically.
  • Filters use and only. or is not supported, and filter values are capped at 512 characters. Stick to userName eq for the common case.
  • DELETE deactivates, it doesn't erase. Users go inactive and stay for audit. A hard erase is a separate portal flow.
  • Discovery needs the token. Even /ServiceProviderConfig requires the bearer token — an anti-fingerprinting choice. An IdP whose discovery probe expects anonymous metadata must be told to send the token.

Troubleshooting#

SymptomCauseFix
401 invalidCredentials on every callMissing, malformed, or revoked token; or no token on the discovery probe.Confirm the IdP sends Authorization: Bearer aforo_scim_… on all requests. If the token was revoked, mint a new one and update the IdP.
IdP "Test Connection" fails at /ServiceProviderConfigThe IdP probes discovery without the bearer token.Ensure the auth header is configured before the test step — Aforo gates metadata too.
400 invalidFilterUnsupported filter — an or clause, an unknown attribute, or a value over 512 chars.Use userName eq "…" or and-joined supported attributes (eq ne co sw ew pr).
412 Precondition Failed on updateStale If-Match ETag — the record changed since the IdP last read it.Re-GET the user, read meta.version, retry with the fresh ETag.
Duplicate user not created (returns existing)Expected — POST is idempotent on externalId / email.Update via PATCH/PUT, not a second POST. Two real people must have distinct externalId + email.
Group + members synced, but users still can't reach a productGroups are an audit mirror in v1 — they don't grant entitlements.Grant access via offerings/subscriptions in the operator console, not SCIM groups.
Offboarded user still shows activeThe IdP unassigned but didn't send DELETE / PATCH active:false.Enable "Deactivate Users" in the IdP's provisioning settings; confirm with the filter call above.

Set up, your directory becomes the single source of truth for who can reach your storefront — provisioning and deprovisioning happen on your IdP's schedule, audited end to end in the SCIM Provisioning activity log. The complete request/response schemas live in the API reference.