Skip to content

SCIM Inbound Provisioning

AuthHero implements a SCIM 2.0 server so an upstream identity provider (Okta, Microsoft Entra ID, OneLogin, …) can provision, update, and deprovision users into an AuthHero tenant automatically. This follows the Auth0 model: SCIM is inbound provisioning attached to an enterprise connection, authenticated with a dedicated bearer token and configured through the Management API.

  • Users only. Groups (/Groups) are not supported yet.
  • Per connection. Each connection has its own SCIM endpoint and its own tokens.
  • Standard pipeline. Provisioned users flow through the normal user pipeline, so hooks and the outbox fire exactly as they do for any other user write.

The SCIM endpoint

Each connection exposes a SCIM base URL:

text
https://{your-tenant-domain}/scim/v2/connections/{connection_id}

Requests authenticate with a per-connection bearer token:

text
Authorization: Bearer {scim-token}

The token is bound to a single connection — a token minted for one connection cannot provision another.

Enabling SCIM on a connection

SCIM is configured through the Management API. All routes are guarded by the *:scim_config and *:scim_token scopes.

1. Create the SCIM configuration

http
POST /api/v2/connections/{connection_id}/scim-configuration
Content-Type: application/json

{
  "user_id_attribute": "externalId",
  "mapping": []
}

Both fields are optional; omitting mapping applies the default mapping (below).

Other configuration routes:

MethodPathDescription
GET/api/v2/connections/{id}/scim-configurationRead the configuration
PATCH/api/v2/connections/{id}/scim-configurationUpdate user_id_attribute / mapping
DELETE/api/v2/connections/{id}/scim-configurationRemove SCIM (also deletes its tokens)
GET/api/v2/connections/{id}/scim-configuration/default-mappingThe default attribute mapping

2. Mint a SCIM token

http
POST /api/v2/connections/{connection_id}/scim-configuration/tokens
Content-Type: application/json

{ "scopes": [], "valid_until": "2027-01-01T00:00:00Z" }

The response contains the raw token once — it is stored only as a hash and cannot be retrieved later. Copy it into your IdP's SCIM configuration.

scopes restricts what the token may do: get:users, post:users, put:users, patch:users, delete:users (a POST /Users/.search counts as get:users). A token minted without scopes may perform every supported operation; a scoped token gets a 403 for anything it was not granted. Tokens are deleted through the connection they belong to.

MethodPathDescription
GET/scim-configuration/tokensList token metadata (never the secret)
POST/scim-configuration/tokensMint a token (raw value returned once)
DELETE/scim-configuration/tokens/{token_id}Revoke a token

Supported SCIM operations

MethodPathNotes
GET/UsersList; supports filter, startIndex (1-based), count (clamped to 200, the advertised filter.maxResults)
POST/UsersCreate (409 with scimType: uniqueness on duplicate)
POST/Users/.searchFilter-based search (filter in the request body)
GET/Users/{id}{id} is the AuthHero user_id
PUT/Users/{id}Full replace
PATCH/Users/{id}Partial update (RFC 7644 PatchOp)
DELETE/Users/{id}Hard delete (see below)
GET/ServiceProviderConfig, /ResourceTypes, /SchemasDiscovery documents

Filtering

The filter parameter supports the operators Okta and Entra use for provisioning: eq combined with and / or and parentheses. The common lookup-before-create case — userName eq "..." or externalId eq "..." — resolves with a targeted query and works at any connection size. Unsupported operators return a SCIM 501 with scimType: invalidFilter.

http
GET /Users?filter=userName eq "[email protected]"

A filter answer is always evaluated over the connection's complete user set: any other filter shape is evaluated in memory, so if the connection holds more than 1,000 users the request fails with a SCIM 400 and scimType: tooMany rather than reporting a user past that boundary as absent (which a provisioning client would read as "create a duplicate"). Narrow such a filter to userName or externalId.

PATCH

add, replace, and remove are supported, including pathless value merges, dotted paths (name.givenName), and value-filtered multi-valued paths (emails[type eq "work"].value). Microsoft Entra's deactivation PATCH is handled:

json
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [{ "op": "replace", "path": "active", "value": false }]
}

Attribute mapping

The SCIM resource id is always the AuthHero user_id. The IdP-assigned externalId is stored per connection and is used for lookups. The default mapping mirrors Auth0's:

SCIM attributeAuthHero field
userNameemail (or username when it isn't an address)
emails[primary eq true].valueemail
name.givenNamegiven_name
name.familyNamefamily_name
displayNamename
activeinverse of blocked
externalIdstored per connection

Deactivation (active: false)

Setting active: false (via PUT or PATCH) blocks the AuthHero user and revokes their sessions and refresh tokens, matching Auth0. A blocked user cannot log in or refresh tokens. Setting active: true again unblocks them — provisioning never hard-deletes on deactivation, so the reactivation path Entra expects is preserved.

DELETE /Users/{id} is different and irreversible: it revokes the user's sessions and then removes the user record and its externalId mapping outright. There is no reactivation after a delete; deactivate with active: false if the user may come back.

Configuring your IdP

  • Microsoft Entra ID — in the enterprise application's Provisioning settings, set the Tenant URL to the SCIM base URL above and the Secret Token to a minted SCIM token. Validate with the Entra SCIM Validator.
  • Okta — enable provisioning on the app integration, set the SCIM connector base URL and the OAuth bearer token, and run Okta's SCIM spec tests.

SCIM can only be enabled on enterprise-strategy connections (SAML, OIDC, Okta Workforce, Entra) — the same set Auth0 gates SCIM to.

Audit logging

Every successful SCIM operation writes a Successful SCIM Operation (sscim) audit log entry, tagged with the connection and the affected user, so provisioning activity appears alongside the rest of the tenant's audit trail.

Limitations

  • No Groups. Only the /Users resource is implemented.
  • Default mapping. The per-connection mapping is stored but the provisioning path currently applies the default mapping; custom attribute maps are a planned enhancement.
  • PUT replaces the attributes it carries. Attributes omitted from a PUT body (and attributes cleared with a remove PATCH op) are left as they are rather than being emptied, because the user store has no representation for "clear this field".
  • Filters other than a single userName/externalId equality are evaluated in memory and are refused with 400 / tooMany on connections holding more than 1,000 users (see Filtering).

Dual-licensed: AGPL-3.0-only or commercial license.