SCIM 2.0 Integration Guide
Kordon supports SCIM (System for Cross-domain Identity Management) v2.0 for automated user and group provisioning from identity providers like:
- Microsoft Azure Active Directory (Entra ID)
- Okta
- OneLogin
- Google Workspace
- Any SCIM 2.0 compliant identity provider
This enables enterprise customers to automatically sync their employee directory with Kordon, eliminating manual user management.
Authentication
Section titled “Authentication”SCIM Token Management
Section titled “SCIM Token Management”SCIM tokens are managed through the Kordon UI by administrators:
-
Creating Tokens (Admin Only):
- Navigate to Settings → Integrations
- Scroll to SCIM Integrations section
- Click Add SCIM Token
- Enter a descriptive title (e.g., “Azure AD Production”, “Okta Staging”)
- Token is generated and shown once - copy it immediately
- Each token creates a dedicated bot user for audit trail
-
Audit Trail:
- Every SCIM change (user create/update/delete, group membership) is tracked
- Bot user named:
SCIM Integration: {token_title} - Changes appear in changelogs with
[I]indicator - Full audit history retained in event store
-
Token Revocation:
- Delete token from UI → immediate invalidation
- No server restart required
- IdP will receive 401 Unauthorized on next sync
-
Security:
- Tokens stored securely in database (hashed recommended, currently plaintext)
- Admin-only access to token management
- Each integration can have separate token for isolation
- Bot users have
adminrole (required for user/group management)
Configuration
Section titled “Configuration”1. Create SCIM Token via UI
Section titled “1. Create SCIM Token via UI”SCIM tokens are managed through the Kordon web interface (admin-only):
-
Navigate to Settings → Integrations
- Log in as an administrator
- Go to Settings menu
- Click “Integrations”
-
Create a New SCIM Token
- Scroll to “SCIM Integrations” section
- Click “Add SCIM Token” button
- Enter a descriptive title (e.g., “Azure AD Production”, “Okta Staging”)
- Click “Create”
-
Copy the Token
- Token is displayed once in a modal
- Copy it immediately and save securely
- You will not be able to see it again
- The modal also shows your SCIM Base URL
-
Configure Your Identity Provider
- Use the copied token as “Bearer Token” or “Secret Token”
- Use the displayed SCIM Base URL (e.g.,
https://your-domain.com/scim/v2) - See identity provider setup guides below
Security Features:
- Admin-only token management
- Each token creates a dedicated bot user for audit trail
- Bot user email:
scim-{uuid}@kordon.app - All SCIM changes tracked in changelog with [I] indicator
- IdP receives 401 Unauthorized on next sync after revocation
Token Management:
- Create separate tokens for each environment (dev/staging/production)
- Use descriptive titles to identify which IdP integration each token is for
- Revoke tokens by deleting them from the UI
- Monitor SCIM activity in user/group changelogs (look for [I] indicator)
Identity Provider Setup
Section titled “Identity Provider Setup”Microsoft Azure AD (Entra ID)
Section titled “Microsoft Azure AD (Entra ID)”Step 1: Create Enterprise Application
- Azure Portal → Enterprise Applications → New Application
- Create your own application → “Kordon SCIM Integration”
- Select “Non-gallery application”
Step 2: Configure Provisioning
- Select Provisioning → Get Started
- Provisioning Mode: Automatic
- Admin Credentials:
- Tenant URL:
https://your-kordon-domain.com/scim/v2 - Secret Token: Your SCIM token from Kordon (Settings → Integrations)
- Tenant URL:
- Test Connection → Save
Step 3: Attribute Mappings
Default mappings work out of the box:
userPrincipalName→userName(email)displayName→name.formattedgivenName→name.givenNamesurname→name.familyNamemailNickname→externalId(Azure AD object ID)accountEnabled→active
Optional: Map Roles
To sync user roles from Azure AD to Kordon:
- Attribute Mappings → Show advanced options → Edit attribute list for Kordon
- Add custom attribute:
roles - Add mapping:
- Azure AD Attribute: Choose an attribute or expression
- Kordon Attribute:
roles[primary eq "True"].value - Mapping type:
Expression - Expression:
Switch([extensionAttribute1], "admin", "manager", "auditor", "user")
Role Mapping Options:
- Option 1: Custom attribute - Store role in user’s
extensionAttribute1-15 - Option 2: App Role assignment - Use Azure AD app roles (requires custom claim mapping)
- Option 3: Group membership - Map specific Azure AD groups to groups.
Step 4: Assign Users
- Users and groups → Add user/group
- Select users/groups to provision
- Assign
Step 5: Start Provisioning
- Provisioning → Start provisioning
- Monitor: Provisioning → Provisioning logs
Sync Schedule: Every 40 minutes (Azure default)
Step 1: Create SCIM Integration
- Okta Admin Console → Applications → Create App Integration
- Sign-On Method: SAML 2.0 or Custom
- Configure SCIM under Provisioning tab
Step 2: SCIM Configuration
- Integration → Provisioning → Configure API Integration
- Enable API integration
- Base URL:
https://your-kordon-domain.com/scim/v2 - API Token: Your SCIM token from Kordon (Settings → Integrations)
- Test API Credentials
Step 3: Provisioning Settings Enable:
- Create Users
- Update User Attributes
- Deactivate Users
Optional:
- Sync Password (not applicable for Kordon)
Step 4: Attribute Mappings Default Okta → SCIM mappings work:
email→userNamefirstName lastName→name.formattedfirstName→name.givenNamelastName→name.familyNameid→externalId
Step 5: Assign Users
- Assignments → Assign → Assign to People/Groups
- Select users to provision
Sync Schedule: Real-time for assignments, hourly for updates
Google Workspace
Section titled “Google Workspace”Note: Google Workspace SCIM support is limited and may require custom app configuration.
Step 1: Create Custom SAML App
- Admin Console → Apps → Web and mobile apps → Add custom SAML app
- Configure SAML SSO
Step 2: Enable User Provisioning (if available)
- User provisioning → Setup
- API Endpoint:
https://your-kordon-domain.com/scim/v2 - Authorization: Bearer token with your SCIM token from Kordon (Settings → Integrations)
Limitations:
- Google Workspace SCIM support varies by subscription tier
- May require Google Workspace Enterprise or Education editions
- Consider using direct API integration as alternative
SCIM Endpoints
Section titled “SCIM Endpoints”Service Provider Configuration
Section titled “Service Provider Configuration”GET /scim/v2/ServiceProviderConfigAuthorization: Bearer {token}Returns capabilities and configuration of the SCIM server.
Resource Types
Section titled “Resource Types”GET /scim/v2/ResourceTypesAuthorization: Bearer {token}Lists supported resource types (User, Group).
Schemas
Section titled “Schemas”GET /scim/v2/SchemasAuthorization: Bearer {token}Returns SCIM schemas for all resources.
User Operations
Section titled “User Operations”List Users
Section titled “List Users”GET /scim/v2/Users?startIndex=1&count=100Authorization: Bearer {token}Query Parameters:
startIndex- Pagination offset (1-based)count- Results per page (default: 100)filter- SCIM filter expression
Filter Examples:
?filter=userName eq "user@example.com"?filter=externalId eq "azure-ad-object-id"?filter=active eq trueGet User
Section titled “Get User”GET /scim/v2/Users/{id}Authorization: Bearer {token}Returns user by Kordon’s internal UUID.
Create User
Section titled “Create User”POST /scim/v2/UsersAuthorization: Bearer {token}Content-Type: application/scim+json
{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "externalId": "azure-ad-12345", "userName": "john.doe@company.com", "name": { "formatted": "John Doe", "givenName": "John", "familyName": "Doe" }, "emails": [{ "value": "john.doe@company.com", "type": "work", "primary": true }], "active": true}Auto-created Resources:
- Personal group (1-member group for ownership)
- User record with default
role: "user"
Update User (PUT - Full Replace)
Section titled “Update User (PUT - Full Replace)”PUT /scim/v2/Users/{id}Authorization: Bearer {token}Content-Type: application/scim+json
{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "userName": "john.doe@company.com", "name": { "formatted": "John A. Doe", "givenName": "John", "familyName": "Doe" }, "active": true}Update User (PATCH - Partial Update)
Section titled “Update User (PATCH - Partial Update)”PATCH /scim/v2/Users/{id}Authorization: Bearer {token}Content-Type: application/scim+json
{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{ "op": "replace", "path": "active", "value": false }]}Common PATCH operations:
- Deactivate:
{"op": "replace", "path": "active", "value": false} - Activate:
{"op": "replace", "path": "active", "value": true} - Update name:
{"op": "replace", "path": "name.formatted", "value": "New Name"}
Delete User (Deactivate)
Section titled “Delete User (Deactivate)”DELETE /scim/v2/Users/{id}Authorization: Bearer {token}Note: This performs a soft delete (sets deactivated_at), not hard delete.
Group Operations
Section titled “Group Operations”Groups in SCIM map to UserGroup (with kind='regular') in Kordon. Personal groups are excluded from SCIM operations.
List Groups
Section titled “List Groups”GET /scim/v2/Groups?startIndex=1&count=100Authorization: Bearer {token}Query Parameters:
startIndex- Pagination offset (1-based)count- Results per page (default: 100)filter- SCIM filter expression
Filter Examples:
?filter=displayName eq "Engineers"?filter=displayName co "Marketing"Response:
{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], "totalResults": 10, "startIndex": 1, "itemsPerPage": 10, "Resources": [ { "id": "341701d0-86f2-4a58-af1c-25bc43705394", "externalId": null, "displayName": "Engineers", "members": [ { "value": "7f341270-5408-410e-9d88-408911a4a8ff", "display": "Juhan Juku" }, { "value": "93f178e7-1c05-4997-97d0-0c22001ce2dc", "display": "Juuri Puuri" } ], "meta": { "resourceType": "Group", "created": "2025-09-04T12:04:27Z", "lastModified": "2025-10-16T14:02:35Z", "location": "http://localhost:4000/scim/v2/Groups/341701d0-86f2-4a58-af1c-25bc43705394" }, "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"] } ]}Get Group
Section titled “Get Group”GET /scim/v2/Groups/{id}Authorization: Bearer {token}Returns a single group with members list.
Create Group
Section titled “Create Group”POST /scim/v2/GroupsAuthorization: Bearer {token}Content-Type: application/scim+json
{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "displayName": "New Team", "externalId": "azure-ad-object-id-123", "members": [ { "value": "user-uuid-1", "display": "John Doe" }, { "value": "user-uuid-2", "display": "Jane Smith" } ]}Creates a new UserGroup with kind='regular' and adds specified members.
Update Group (Full Replace)
Section titled “Update Group (Full Replace)”PUT /scim/v2/Groups/{id}Authorization: Bearer {token}Content-Type: application/scim+json
{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "displayName": "Updated Team Name", "externalId": "azure-ad-object-id-123", "members": [ { "value": "user-uuid-1", "display": "John Doe" } ]}Replaces all group attributes including members. Missing members are removed.
Update Group (Partial Update)
Section titled “Update Group (Partial Update)”PATCH /scim/v2/Groups/{id}Authorization: Bearer {token}Content-Type: application/scim+json
{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ { "op": "add", "path": "members", "value": [ { "value": "user-uuid-3", "display": "New Member" } ] }, { "op": "remove", "path": "members[value eq \"user-uuid-2\"]" }, { "op": "replace", "path": "displayName", "value": "Renamed Team" } ]}Partial updates using SCIM PATCH operations:
add- Add members to groupremove- Remove members from groupreplace- Update group name or other attributes
Delete Group
Section titled “Delete Group”DELETE /scim/v2/Groups/{id}Authorization: Bearer {token}Note: This performs a soft delete (sets deleted_at via paranoia gem).
Then create app/controllers/scim/v2/groups_controller.rb similar to users controller.
Testing SCIM Integration
Section titled “Testing SCIM Integration”Manual Testing with cURL
Section titled “Manual Testing with cURL”1. Test Authentication:
export SCIM_TOKEN="your-bearer-token"export KORDON_URL="https://your-kordon-domain.com"
curl -X GET \ "$KORDON_URL/scim/v2/ServiceProviderConfig" \ -H "Authorization: Bearer $SCIM_TOKEN" \ -H "Content-Type: application/scim+json"2. List Users:
curl -X GET \ "$KORDON_URL/scim/v2/Users" \ -H "Authorization: Bearer $SCIM_TOKEN" \ -H "Content-Type: application/scim+json"3. Create Test User:
curl -X POST \ "$KORDON_URL/scim/v2/Users" \ -H "Authorization: Bearer $SCIM_TOKEN" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "userName": "test.user@example.com", "name": { "formatted": "Test User", "givenName": "Test", "familyName": "User" }, "emails": [{ "value": "test.user@example.com", "type": "work", "primary": true }], "active": true, "externalId": "test-external-id-123" }'4. Filter Users:
# By emailcurl -X GET \ "$KORDON_URL/scim/v2/Users?filter=userName%20eq%20%22test.user@example.com%22" \ -H "Authorization: Bearer $SCIM_TOKEN"
# By external IDcurl -X GET \ "$KORDON_URL/scim/v2/Users?filter=externalId%20eq%20%22test-external-id-123%22" \ -H "Authorization: Bearer $SCIM_TOKEN"Testing with Postman
Section titled “Testing with Postman”Import this collection for comprehensive SCIM testing:
{ "info": { "name": "Kordon SCIM v2", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "variable": [ { "key": "base_url", "value": "https://your-kordon-domain.com" }, { "key": "bearer_token", "value": "your-scim-bearer-token" } ], "auth": { "type": "bearer", "bearer": [ { "key": "token", "value": "{{bearer_token}}" } ] }}Troubleshooting
Section titled “Troubleshooting”Authentication Failures
Section titled “Authentication Failures”Error: 401 Unauthorized
Causes:
- Missing
Authorizationheader - Invalid bearer token
- SCIM token deleted or revoked in Kordon UI
- Token not created yet
Solution:
-
Verify token exists:
- Log into Kordon as admin
- Go to Settings → Integrations
- Check if SCIM token is listed in “SCIM Integrations” section
-
Create new token if needed:
- Click “Add SCIM Token”
- Enter descriptive title
- Copy the generated token (shown once)
- Update your identity provider configuration
-
No restart required:
- Token changes are immediate
- IdP will authenticate with new token on next sync
User Creation Fails
Section titled “User Creation Fails”Error: 422 Unprocessable Entity or validation errors
Causes:
- Email already exists (must be unique)
- Missing required fields (userName, name)
- Invalid email format
Solution:
- Check user doesn’t already exist
- Ensure all required SCIM attributes provided
- Validate email format
External ID Conflicts
Section titled “External ID Conflicts”Error: Duplicate external_id violation
Cause: IdP trying to create user with existing external_id
Solution:
- Check if user was already provisioned
- IdP may need to query first:
GET /scim/v2/Users?filter=externalId eq "..." - If exists, use PUT/PATCH to update instead of POST