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.
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.
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:
- A SCIM bearer token you mint in Aforo. Your IdP sends it as
Authorization: Bearer …on every request. Aforo stores only a SHA-256 hash of it — the plaintext is shown once. - The SCIM endpoint URL you paste into your IdP. Every user lifecycle event your IdP emits becomes a SCIM call to
/scim/v2/Users. - The mapping. Your IdP sends each user's stable directory ID as
externalIdand their email asuserName. Aforo keys onexternalIdso 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:
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:
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 action | SCIM call | What Aforo does |
|---|---|---|
| Assign user to app | POST /Users | Creates 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 profile | PATCH /Users/{id} | Applies the Operations array — replace/add/remove on active, userName, name.givenName, name.familyName, externalId. |
| Replace whole record | PUT /Users/{id} | Full overwrite. Honors If-Match (see ETags below). |
| Unassign / offboard | DELETE /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 / search | GET /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.
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.
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 & path | Auth | What it does |
|---|---|---|
GET /scim/v2/ServiceProviderConfig | SCIM token | Capability discovery (patch ✓, filter ✓, sort ✓, etag ✓, bulk ✕, changePassword ✕) |
GET /scim/v2/Schemas · /ResourceTypes | SCIM token | User + Group schema and resource-type definitions |
GET /scim/v2/Users | SCIM token | List / filter / sort / paginate users |
POST /scim/v2/Users | SCIM token | Create user (idempotent on externalId → email) |
GET·PUT·PATCH·DELETE /scim/v2/Users/{id} | SCIM token | Fetch / replace / patch / soft-deactivate a user |
GET·POST /scim/v2/Groups · …/{id} | SCIM token | Group CRUD + membership (audit mirror, v1) |
POST·GET·DELETE /api/v1/storefront/scim/tokens | OWNER / ADMIN | Mint (plaintext once) / list / revoke SCIM tokens |
GET /api/v1/storefront/scim/config | OWNER / ADMIN | Your 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.
changePasswordisfalse— the storefront portal owns password reset. SCIM never carries credentials. - No bulk endpoint.
bulkisfalse; IdPs fall back to per-user requests automatically. - Filters use
andonly.oris not supported, and filter values are capped at 512 characters. Stick touserName eqfor the common case. DELETEdeactivates, it doesn't erase. Users go inactive and stay for audit. A hard erase is a separate portal flow.- Discovery needs the token. Even
/ServiceProviderConfigrequires the bearer token — an anti-fingerprinting choice. An IdP whose discovery probe expects anonymous metadata must be told to send the token.
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
401 invalidCredentials on every call | Missing, 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 /ServiceProviderConfig | The IdP probes discovery without the bearer token. | Ensure the auth header is configured before the test step — Aforo gates metadata too. |
400 invalidFilter | Unsupported 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 update | Stale 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 product | Groups 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 active | The 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.