SCIM 2.0 Provisioning

ARX implements the SCIM 2.0 protocol (RFC 7644) to enable automated user and group lifecycle management from enterprise identity providers. This guide covers all SCIM endpoints, bearer token setup, user and group operations, group-to-role mapping, and configuration for Okta and Microsoft Entra ID.

Base URL

All SCIM endpoints are served under the /scim/v2 path prefix:

https://api.arxsec.io/scim/v2

Authentication

SCIM requests are authenticated using a static bearer token, not JWT. Each organization has a dedicated SCIM token hashed with bcrypt and stored in the scim_tokens table.

Include the token in the Authorization header:

Authorization: Bearer <your-scim-token>

The token is validated on every request by comparing the raw token against stored bcrypt hashes. The token is scoped to a single organization -- all user and group operations are restricted to that organization. The last_used_at timestamp is updated on each successful authentication.

Generating a SCIM Token

SCIM tokens are provisioned by an administrator. The raw token value should be generated using a cryptographically secure random generator (minimum 32 bytes):

openssl rand -base64 32

Hash the token with bcrypt and store it in the scim_tokens table:

INSERT INTO scim_tokens (org_id, token_hash, description)
VALUES ('<org-id>', '<bcrypt-hash>', 'Okta SCIM provisioning');

Store the raw token securely -- it cannot be recovered from the bcrypt hash.

Endpoints

ARX implements 14 SCIM endpoints across discovery, user management, and group management.

Discovery Endpoints

Method Endpoint Description
GET /scim/v2/ServiceProviderConfig Returns service provider configuration and supported features
GET /scim/v2/Schemas Returns the User and Group schemas with attribute definitions
GET /scim/v2/ResourceTypes Returns supported resource types (User, Group)

User Endpoints

Method Endpoint Description
GET /scim/v2/Users List users with optional filtering and pagination
GET /scim/v2/Users/{id} Retrieve a single user by ID
POST /scim/v2/Users Create a new user
PUT /scim/v2/Users/{id} Full replacement of a user resource
PATCH /scim/v2/Users/{id} Partial update using SCIM PatchOp
DELETE /scim/v2/Users/{id} Deactivate a user (soft delete)

Group Endpoints

Method Endpoint Description
GET /scim/v2/Groups List groups with optional filtering and pagination
GET /scim/v2/Groups/{id} Retrieve a single group by ID
POST /scim/v2/Groups Create a new group
PATCH /scim/v2/Groups/{id} Update group membership and attributes
DELETE /scim/v2/Groups/{id} Delete a group and its memberships

Service Provider Configuration

The /scim/v2/ServiceProviderConfig endpoint advertises the following capabilities:

Feature Supported
Patch Yes
Bulk No
Filter Yes (max 200 results)
Change Password No
Sort No
ETag No
Authentication OAuth Bearer Token

User Lifecycle

Creating Users

When the IdP pushes a new user, ARX creates a record in the users table:

POST /scim/v2/Users
{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "userName": "jane@acme.com",
  "name": {
    "givenName": "Jane",
    "familyName": "Doe"
  },
  "displayName": "Jane Doe",
  "emails": [{"value": "jane@acme.com", "type": "work", "primary": true}],
  "active": true,
  "externalId": "okta-user-id-12345",
  "roles": [{"value": "deployer"}]
}

Key behaviors:

Updating Users

Full replacement (PUT) replaces all mutable attributes:

PUT /scim/v2/Users/{id}
{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "userName": "jane@acme.com",
  "name": {"givenName": "Jane", "familyName": "Smith"},
  "displayName": "Jane Smith",
  "active": true,
  "externalId": "okta-user-id-12345",
  "roles": [{"value": "admin"}]
}

Partial update (PATCH) supports add, replace, and remove operations:

PATCH /scim/v2/Users/{id}
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [
    {"op": "replace", "path": "active", "value": false},
    {"op": "replace", "path": "displayName", "value": "Jane Smith-Doe"}
  ]
}

Supported PATCH paths: active, userName, displayName, name.givenName, name.familyName, name.formatted, externalId. Pathless operations with a value object are also supported for bulk attribute replacement, including roles.

Deactivating Users

When a user is removed from the IdP application, the IdP sends a DELETE request:

DELETE /scim/v2/Users/{id}

ARX performs a soft delete by setting active=false rather than removing the user record. This preserves audit history and prevents data loss. Returns 204 No Content.

Filtering Users

The list endpoint supports SCIM filter syntax for two attributes:

GET /scim/v2/Users?filter=userName eq "jane@acme.com"
GET /scim/v2/Users?filter=externalId eq "okta-user-id-12345"

Pagination

Pagination is controlled with startIndex (1-based) and count (default 100, max 200) query parameters.

Group Synchronization

Creating Groups

When the IdP pushes a group, ARX creates it with an automatic role mapping if the group name contains a recognized role:

POST /scim/v2/Groups
{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
  "displayName": "ARX Deployers",
  "externalId": "okta-group-id-67890",
  "members": [
    {"value": "user-uuid-1"},
    {"value": "user-uuid-2"}
  ]
}

If the group's displayName contains one of the valid role names (admin, deployer, auditor, viewer), ARX automatically sets the role_mapping field. All members of that group will have their ARX role synchronized to match.

Updating Group Membership (PATCH)

Adding members:

{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [
    {"op": "add", "path": "members", "value": [{"value": "user-uuid-3"}]}
  ]
}

Removing members:

{
  "Operations": [
    {"op": "remove", "path": "members[value eq \"user-uuid-2\"]"}
  ]
}

Replacing all members:

{
  "Operations": [
    {"op": "replace", "path": "members", "value": [{"value": "user-uuid-1"}, {"value": "user-uuid-4"}]}
  ]
}

Filtering Groups

GET /scim/v2/Groups?filter=displayName eq "ARX Deployers"
GET /scim/v2/Groups?filter=externalId eq "okta-group-id-67890"

Deleting Groups

DELETE /scim/v2/Groups/{id}

Deleting a group removes all membership records. The users themselves are not affected. Returns 204 No Content.

Group-to-Role Mapping

ARX bridges SCIM groups to its RBAC model through the role_mapping field on the groups table.

Automatic mapping: If a group's displayName contains a valid role name (case-insensitive), the mapping is set automatically at creation time.

Group Display Name Auto-Mapped Role
"ARX Admins" admin
"ARX Deployers" deployer
"Security Auditors" auditor
"ARX Viewers" viewer

How role sync works:

  1. When members are added to a group with a role_mapping, those users' role field in the users table is updated to match.
  2. When group membership is replaced or modified via PATCH, all current members have their roles synchronized.
  3. The _sync_member_roles function iterates over all group members and updates their roles.

Manual mapping: For groups that do not follow the naming convention, administrators can set the role_mapping field directly in the database.

Configuring Okta SCIM

  1. In the Okta Admin Console, navigate to your ARX SAML/OIDC application.
  2. Go to the Provisioning tab and click Configure API Integration.
  3. Check Enable API Integration.
  4. Set the SCIM connector base URL to https://api.arxsec.io/scim/v2.
  5. Set Unique identifier field for users to userName.
  6. Under Authentication Mode, select HTTP Header and paste your SCIM bearer token.
  7. Click Test API Credentials to verify connectivity.
  8. Under Provisioning > To App, enable:
  9. Create Users
  10. Update User Attributes
  11. Deactivate Users
  12. Under Push Groups, add the groups you want to sync (e.g., "ARX Admins", "ARX Deployers").

Configuring Microsoft Entra ID SCIM

  1. In the Azure Portal, navigate to Microsoft Entra ID > Enterprise applications > Your ARX app.
  2. Go to Provisioning and set Provisioning Mode to Automatic.
  3. Under Admin Credentials:
  4. Tenant URL: https://api.arxsec.io/scim/v2
  5. Secret Token: Your SCIM bearer token
  6. Click Test Connection to verify.
  7. Under Mappings, verify attribute mappings:
  8. userPrincipalName -> userName
  9. displayName -> displayName
  10. givenName -> name.givenName
  11. surname -> name.familyName
  12. Switch([IsSoftDeleted], , "False", "True", "True", "False") -> active
  13. Under Settings, set the scope to Sync only assigned users and groups.
  14. Set Provisioning Status to On.

Entra ID runs a provisioning cycle approximately every 40 minutes. The initial cycle may take longer depending on the number of users and groups.

Content Type

All SCIM responses use the application/scim+json media type as required by RFC 7644.

Error Responses

SCIM errors follow the standard format:

{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
  "detail": "User with userName 'jane@acme.com' already exists",
  "status": "409",
  "scimType": "uniqueness"
}
Status Meaning
400 Invalid request (missing required fields)
401 Invalid or missing SCIM bearer token
404 Resource not found
409 Conflict (duplicate userName or displayName)
500 Internal server error

Troubleshooting

Symptom Cause Resolution
401 Unauthorized Invalid SCIM bearer token Verify the token matches the bcrypt hash in the scim_tokens table.
409 Conflict on user creation Duplicate userName in the org The IdP should use the existing user's ID for subsequent operations.
Users created with wrong role Role mapping not applied Verify that SCIM groups with role mappings are pushed before or alongside user creation.
Entra ID provisioning errors Attribute mapping mismatch Check provisioning logs in the Azure Portal and verify mappings.
Role not syncing Group has no role_mapping Verify the group displayName contains a valid role name, or set role_mapping manually.