SCIM 2.0 Protocol
The Problem: Manual Provisioning Does Not Scale
When a company hires a new employee, that person needs accounts in dozens of systems: email, chat, project management, CRM, source control, identity providers, and every SaaS product the team uses. When that employee leaves, all those accounts need to be deactivated — immediately, not “when someone remembers.”
Manual provisioning creates three categories of problems:
| Problem | Impact |
|---|---|
| Onboarding delay | New hires wait hours or days for account creation, losing productive time |
| Offboarding gaps | Departed employees retain access to systems because someone forgot to deprovision them |
| Orphan accounts | Accounts that belong to no one accumulate over time, creating security liability |
| Attribute drift | A user’s department, title, or manager changes in HR but not in downstream systems |
| Compliance risk | Auditors ask “show me that terminated employee X lost access within 24 hours” — and the answer is often “we cannot prove that” |
| IT burden | Provisioning and deprovisioning is repetitive, error-prone work that scales linearly with headcount |
The solution is automated provisioning: the identity provider (Okta, Azure AD, OneLogin, JumpCloud) pushes user lifecycle events to downstream applications in real time. But every SaaS product implementing its own provisioning API would be chaos — different schemas, different protocols, different authentication methods.
What SCIM Solves
SCIM (System for Cross-domain Identity Management) is a standardized REST API for user provisioning, defined in two RFCs:
- RFC 7643: The SCIM Core Schema — defines the data model (User, Group, EnterpriseUser)
- RFC 7644: The SCIM Protocol — defines the REST operations, filtering, pagination, and bulk operations
SCIM gives every identity provider and every service provider a common language. When Okta needs to create a user in Auris, it speaks SCIM. When Azure AD needs to disable a user, it speaks SCIM. The integration is the same regardless of which IdP and which service provider are involved.
SCIM is an inbound provisioning protocol in Auris. The IdP pushes changes to Auris — Auris does not poll the IdP. This means provisioning is near-real-time: when an admin assigns a user in Okta, the SCIM POST arrives at Auris within seconds.
SCIM Resource Types
SCIM defines two core resource types:
Users (/Users)
The User resource represents an identity with attributes mapped to the SCIM Core Schema:
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "usr_abc123",
"externalId": "okta-user-id-456",
"userName": "[email protected]",
"name": {
"givenName": "Jane",
"familyName": "Doe",
"formatted": "Jane Doe"
},
"emails": [
{
"value": "[email protected]",
"type": "work",
"primary": true
}
],
"displayName": "Jane Doe",
"active": true,
"title": "Senior Engineer",
"department": "Engineering",
"meta": {
"resourceType": "User",
"created": "2025-01-15T10:30:00Z",
"lastModified": "2025-06-20T14:22:00Z",
"location": "https://auth.example.com/scim/v2/Users/usr_abc123"
}
}| Attribute | SCIM Type | Description |
|---|---|---|
id | String | Auris-assigned unique identifier (read-only) |
externalId | String | IdP-assigned identifier (used for correlation) |
userName | String | Unique login name (typically email) |
name.givenName | String | First name |
name.familyName | String | Last name |
emails | Multi-valued | Email addresses (primary flagged) |
displayName | String | Human-readable full name |
active | Boolean | Account enabled/disabled status |
title | String | Job title |
department | String | Department name |
Groups (/Groups)
The Group resource represents a collection of users:
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"id": "grp_xyz789",
"displayName": "Engineering",
"members": [
{ "value": "usr_abc123", "display": "Jane Doe" },
{ "value": "usr_def456", "display": "John Smith" }
]
}Groups in SCIM map to roles or teams in downstream systems. Auris maps SCIM Groups to Keycloak groups, which can be linked to roles.
SCIM Operations
SCIM uses standard HTTP methods with REST semantics:
Create User
POST /scim/v2/Users
Content-Type: application/scim+json
Authorization: Bearer scim_token_here
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"externalId": "okta-user-789",
"userName": "[email protected]",
"name": {
"givenName": "Jane",
"familyName": "Doe"
},
"emails": [
{ "value": "[email protected]", "type": "work", "primary": true }
],
"active": true
}Response: 201 Created with the full User resource including the Auris-assigned id.
Get User
GET /scim/v2/Users/usr_abc123
Authorization: Bearer scim_token_hereResponse: 200 OK with the full User resource.
List Users
GET /scim/v2/Users?filter=userName eq "[email protected]"&startIndex=1&count=10
Authorization: Bearer scim_token_hereResponse: 200 OK with a ListResponse:
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": 1,
"itemsPerPage": 10,
"startIndex": 1,
"Resources": [
{ "id": "usr_abc123", "userName": "[email protected]", ... }
]
}Replace User (Full Update)
PUT /scim/v2/Users/usr_abc123
Content-Type: application/scim+json
Authorization: Bearer scim_token_here
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "[email protected]",
"name": { "givenName": "Jane", "familyName": "Smith" },
"active": true
}Response: 200 OK with the updated User resource.
Partial Update (PATCH)
SCIM PATCH uses a specific operation format defined in RFC 7644:
PATCH /scim/v2/Users/usr_abc123
Content-Type: application/scim+json
Authorization: Bearer scim_token_here
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "replace", "path": "active", "value": false },
{ "op": "replace", "path": "name.familyName", "value": "Smith" }
]
}PATCH is the most common operation for deprovisioning: the IdP sends { "op": "replace", "path": "active", "value": false } to disable the user without deleting the record.
Delete User
DELETE /scim/v2/Users/usr_abc123
Authorization: Bearer scim_token_hereResponse: 204 No Content. In Auris, DELETE performs a soft-disable (sets active: false) rather than permanent deletion, preserving audit trail integrity.
Filtering (RFC 7644 Section 3.4.2.2)
SCIM defines a filter expression syntax that allows IdPs to query for specific users. Auris implements a recursive descent parser for SCIM filter expressions.
Filter Operators
| Operator | Meaning | Example |
|---|---|---|
eq | Equals | userName eq "[email protected]" |
ne | Not equals | active ne false |
co | Contains | userName co "jane" |
sw | Starts with | userName sw "jane" |
ew | Ends with | emails.value ew "@company.com" |
pr | Present (has value) | title pr |
gt | Greater than | meta.lastModified gt "2025-01-01T00:00:00Z" |
lt | Less than | meta.created lt "2025-06-01T00:00:00Z" |
ge | Greater than or equal | meta.lastModified ge "2025-01-01" |
le | Less than or equal | meta.created le "2025-12-31" |
Compound Filters
Filters can be combined with and and or, and grouped with parentheses:
filter=userName eq "[email protected]" and active eq true
filter=(department eq "Engineering") or (department eq "Product")
filter=userName sw "j" and (active eq true or title pr)Filter to Prisma Translation
Auris converts SCIM filter expressions to Prisma where clauses via a recursive parser:
SCIM filter: userName eq "[email protected]" and active eq true
Parser: tokenize → build AST → convert to Prisma
Prisma where: {
AND: [
{ scimUserName: { equals: "[email protected]" } },
{ isEnabled: true }
]
}The parser (lib/scim/filter-parser.ts) handles operator precedence, parenthetical grouping, dot-notation attribute paths (e.g., name.familyName), and value type coercion (strings, booleans, numbers, dates).
SCIM filters operate on SCIM attribute names (e.g., userName, name.familyName), not on database column names. Auris maps SCIM attributes to Prisma fields via the attribute mapping configuration. If a filter references an unmapped attribute, the filter term is ignored (not rejected), following the SCIM specification’s guidance on best-effort filtering.
Pagination
SCIM uses 1-based index pagination (not cursor-based):
| Parameter | Description | Default |
|---|---|---|
startIndex | 1-based index of the first result to return | 1 |
count | Maximum number of results per page | 100 |
totalResults | Total number of matching resources (returned in response) | — |
itemsPerPage | Actual number of resources in this page (returned in response) | — |
GET /scim/v2/Users?startIndex=1&count=25
{
"totalResults": 150,
"itemsPerPage": 25,
"startIndex": 1,
"Resources": [ ... 25 users ... ]
}
GET /scim/v2/Users?startIndex=26&count=25
{
"totalResults": 150,
"itemsPerPage": 25,
"startIndex": 26,
"Resources": [ ... 25 users ... ]
}Bearer Token Authentication
SCIM endpoints in Auris are protected by bearer token authentication. Each SCIM connection has a unique token configured in the Auris Console.
GET /scim/v2/Users
Authorization: Bearer scim_aBcDeFgHiJkLmNoPqRsTuVwXyZ123456The token is validated against the ScimConnection model. Each connection is scoped to a specific tenant, so tokens from one tenant cannot access another tenant’s SCIM endpoints.
| Security Measure | Description |
|---|---|
| Token hashing | Tokens are stored as SHA-256 hashes, not plaintext |
| Tenant isolation | Each token is bound to a specific tenant |
| Audit logging | Every SCIM operation is logged with the connection ID |
| IP allowlisting | Optional: restrict SCIM access to IdP source IPs via IP Rules |
Auris SCIM Implementation
Architecture
Auris implements inbound SCIM provisioning: the IdP pushes user lifecycle changes to Auris. Auris does not pull from the IdP.
Dual Write: Auris + Keycloak
When a SCIM POST creates a user, Auris performs a dual write:
- Auris Database: Creates a
TenantUserrecord withscimExternalId(the IdP’s identifier) andscimUserName(the IdP’s username) - Keycloak: Creates a user in the tenant’s Keycloak realm with matching attributes
Both writes must succeed. If Keycloak creation fails, the Auris record is rolled back.
Attribute Mapping
SCIM attributes do not always map 1:1 to Auris fields. The ScimAttributeMapping model allows per-connection customization:
Mappings are configured per SCIM connection in the Auris Console (Integrations > SCIM > connection detail > Mappings tab). Each mapping has a direction: INBOUND (IdP to Auris), OUTBOUND (Auris to IdP, for future use), or BOTH.
Bulk Operations
Auris supports SCIM Bulk operations (RFC 7644 Section 3.7) for batch provisioning:
POST /scim/v2/Bulk
Content-Type: application/scim+json
Authorization: Bearer scim_token_here
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:BulkRequest"],
"Operations": [
{
"method": "POST",
"path": "/Users",
"data": {
"userName": "[email protected]",
"name": { "givenName": "Alice", "familyName": "Johnson" },
"active": true
}
},
{
"method": "POST",
"path": "/Users",
"data": {
"userName": "[email protected]",
"name": { "givenName": "Bob", "familyName": "Williams" },
"active": true
}
},
{
"method": "PATCH",
"path": "/Users/usr_old_employee",
"data": {
"Operations": [
{ "op": "replace", "path": "active", "value": false }
]
}
}
]
}Response: Per-operation status codes
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:BulkResponse"],
"Operations": [
{ "method": "POST", "location": "/Users/usr_new1", "status": "201" },
{ "method": "POST", "location": "/Users/usr_new2", "status": "201" },
{ "method": "PATCH", "location": "/Users/usr_old_employee", "status": "200" }
]
}Auris enforces a maximum of 100 operations per bulk request. Each operation is processed independently — a failure in one operation does not prevent others from succeeding.
Bulk operations are especially useful during initial provisioning, when an IdP syncs its entire user directory to Auris for the first time. Without bulk support, this would require one HTTP request per user, which is impractical for directories with thousands of users.
User Lifecycle
The complete SCIM-driven user lifecycle looks like this:
The key benefit over manual provisioning is the deprovisioning speed. When an employee is terminated, the IdP admin removes their access in one place, and all connected applications (including Auris) disable the user within seconds. There is no “forgot to remove access from that one app” risk.
Comparison: Provisioning Approaches
| Dimension | SCIM 2.0 | Manual Provisioning | JIT Provisioning | LDAP Sync |
|---|---|---|---|---|
| Provisioning speed | Near-real-time (seconds) | Hours to days | On first login only | Scheduled (minutes to hours) |
| Deprovisioning speed | Near-real-time (seconds) | Hours to days (if remembered) | Never (no deprovisioning) | Next sync cycle |
| Attribute sync | Real-time push | Manual updates | Login-time only | Scheduled batch |
| Orphan account risk | Low (automatic deprovisioning) | High (human error) | High (no deprovisioning) | Medium (depends on sync frequency) |
| Standardization | RFC 7643/7644 (universal) | Proprietary per app | Proprietary per IdP | LDAP protocol (standardized) |
| IdP support | All major IdPs | N/A | Most IdPs via SAML/OIDC | Requires LDAP directory |
| Bulk operations | Yes (RFC 7644 Section 3.7) | N/A | N/A | Yes (batch sync) |
| Network direction | IdP pushes to SP | Admin manually acts | User initiates | SP pulls from directory |
| Compliance | Excellent audit trail | Poor (manual tracking) | Partial (no deprovision proof) | Good (sync logs) |
| Implementation effort | Moderate (implement SCIM endpoints) | None (manual work) | Low (read IdP assertions) | Moderate (LDAP client) |
When to Use SCIM
- Enterprise customers with centralized identity management (Okta, Azure AD, OneLogin)
- Compliance requirements that mandate automated deprovisioning (SOC 2, ISO 27001, HIPAA)
- Large user bases where manual provisioning is impractical
- Multi-app environments where users need consistent access across many services
When JIT Provisioning Is Sufficient
- Small teams where manual provisioning is manageable
- Consumer-facing apps where users self-register
- Apps where deprovisioning is not critical (read-only dashboards, public tools)
SCIM and JIT provisioning are not mutually exclusive. Many Auris tenants use SCIM for provisioning and deprovisioning (user lifecycle management) and JIT provisioning via SAML/OIDC for the actual login flow. SCIM ensures the user exists and is active; SSO handles the authentication.
Security Considerations
Bearer Token Management
SCIM bearer tokens are long-lived secrets that grant full provisioning access. Treat them with the same care as database credentials.
| Practice | Description |
|---|---|
| Rotate regularly | Generate a new token and update the IdP configuration quarterly |
| Store securely | Never commit SCIM tokens to source control or logs |
| Monitor usage | Auris logs every SCIM operation with the connection ID — review for unexpected patterns |
| Limit scope | Each SCIM connection is scoped to one tenant — a compromised token cannot affect other tenants |
IP Allowlisting
For maximum security, restrict SCIM endpoint access to your IdP’s source IP ranges using Auris IP Rules:
IP Rule: ALLOW CIDR: 100.25.0.0/16 Label: "Okta US East"
IP Rule: ALLOW CIDR: 100.26.0.0/16 Label: "Okta US West"
IP Rule: BLOCK CIDR: 0.0.0.0/0 Label: "Block all other SCIM access"This ensures that even if a SCIM token is compromised, it cannot be used from unauthorized networks.
Audit Logging
Every SCIM operation is logged in the Auris audit log with:
- The SCIM connection ID
- The HTTP method and path
- The target user’s
externalIdanduserName - The operation result (success/failure)
- The source IP address
- A timestamp
These logs are available in the Auris Console under Logs and can be streamed to external SIEM systems via Log Streaming.
Related Concepts
- Multi-Tenancy — How tenant isolation applies to SCIM connections and provisioned users
- Hosted Login (Universal Login) — The login flow that SCIM-provisioned users authenticate through
- OAuth 2.0 & OIDC — The authentication protocol used after SCIM provisions the user
- Adaptive MFA & Risk Scoring — Security policies applied to SCIM-provisioned users at login time
- Sessions & Token Rotation — Session management for provisioned users