Skip to content

Overview

Organisations that manage their staff in a central identity provider want mail accounts to follow the life of each employee without manual work. A new hire should have a mailbox on the first day. A change of department should change the groups the employee belongs to. A departure should lock the account and, where the policy requires it, remove the mailbox. SCIM provisioning lets the identity provider carry out these changes on the mail server itself, at the moment they happen in the corporate directory.

SCIM, the System for Cross-domain Identity Management, is the standard protocol for this exchange. It defines a set of HTTP endpoints and JSON resources that represent users and groups. The identity provider acts as the SCIM client: it watches its own directory and sends a request to each application it governs whenever an identity is created, changed, suspended or deleted. The application acts as the SCIM service provider and applies each request to its own accounts. The schema is defined in RFC 7643, and the protocol in RFC 7644.

Stalwart implements the service provider side of the protocol. It receives provisioning requests at /scim/v2 on its HTTP listeners and applies them to its accounts. It never acts as a SCIM client, so it sends no provisioning requests to other systems. SCIM requires an Enterprise license. On a server without one, requests to /scim/v2 are refused with 403 Forbidden and the detail SCIM provisioning is only available in the Enterprise edition. Obtain your trial license at https://license.stalw.art/trial.

A deployment with SCIM involves three parties, shown in the following diagram:

flowchart LR
  HR[HR system or
corporate directory] --> IDP[Identity provider
SCIM client] IDP -->|SCIM 2.0 over HTTPS
POST, PATCH, DELETE| SW[Stalwart
SCIM service provider] USER[Mail client] -->|OAUTHBEARER access token| SW USER -.->|authenticates| IDP SW -.->|validates token| IDP

The identity provider is the SCIM client and the source of truth for identities. Examples are Microsoft Entra ID, Okta, and Keycloak with a SCIM extension. Stalwart holds the mailboxes and the data that belongs to each account.

SCIM only creates and maintains accounts. It does not verify sign-ins, so it is always combined with a directory that does. The deployment SCIM is designed for pairs it with an OpenID Connect directory. In that setup, the identity provider authenticates the users, and Stalwart validates the access tokens the provider issues. SCIM creates each account before its owner signs in for the first time. SCIM also works with LDAP and SQL directories, and there it adds what those directories lack: explicit suspension and deletion of accounts (Provisioning models).

No credential crosses the SCIM interface, because the identity provider holds the credentials. For this reason the server advertises changePassword as unsupported and does not publish a password attribute.

SCIM solves two problems that an OpenID Connect directory has on its own.

The first problem is the start of an account’s life. Without SCIM, an account of an OpenID Connect directory is created the first time its owner signs in. Until then, the address does not exist on the server, so mail sent to a new employee before the first sign-in is rejected. SCIM creates the account as soon as the identity is created upstream, which is typically days before the first sign-in.

The second problem is the end of an account’s life. Without SCIM, accounts are never removed. The server cannot learn that an account was deleted at the identity provider, so the mailbox of a departed employee remains and keeps receiving mail. With SCIM, the identity provider reports both events. A PATCH that sets active to false suspends the account and keeps its contents, and a DELETE removes it.

Stalwart provisions two resource types:

  • Users, which map to user accounts. A user carries its primary address, aliases, display name, locale, time zone and status.
  • Groups, which map to group accounts and carry their membership.

Both resource types carry an externalId. This is the identifier the identity provider uses in its own records. Because externalId can be used in filters, a client can find a resource it provisioned earlier without storing Stalwart’s identifiers.

The server also publishes the discovery endpoints that clients read when they connect: /ServiceProviderConfig, /ResourceTypes and /Schemas. It supports bulk operations and queries sent by POST, and it uses entity tags for optimistic concurrency, so that two clients cannot overwrite each other’s changes unnoticed (Endpoints and protocol support).

Several profiles narrow the SCIM standard for interoperability, and Stalwart follows them with one exception. It follows the SCIM interoperability profile except for the profile’s rule that undefined attributes must be rejected (Undefined attributes). It implements the lifecycle requirements of the IPSIE profile. It also supports cursor-based pagination as defined in RFC 9865.

On the Stalwart side, the setup consists of three decisions, described in Configuration:

  • Which domains the identity provider may provision. This is set by allowScimProvisioning on each Domain.
  • Which account the SCIM client authenticates as, and which API key it presents as a bearer token.
  • Which permissions that account holds. It needs scimAccess, the account management permissions, and every permission of the accounts it manages.

Attribute mapping describes how SCIM attributes are translated to account fields. Identity providers gives the procedure for individual providers.

When an identity provider reports a failed synchronisation, the first place to look is the server’s events. Each SCIM request produces events that record what the server did:

EventLevelMeaning
scim.requestdebugA request was answered; records the method, path, status and processing time
scim.forbiddeninfoA request was refused with 403: a missing permission, an attempt to deactivate the SCIM client’s own account, a tenant or account limit, the account modification rule (You are not authorized to grant permissions: ...), or no Enterprise license
scim.conflictinfoA request was refused with 409 or 412: a duplicate userName, externalId or group name, or a stale entity tag
scim.errordebugAny other error response, with the status, scimType and detail
scim.resource-created, scim.resource-updated, scim.resource-deletedinfoA user or group was created, changed or deleted
scim.querydebugA list or search request was evaluated
scim.attribute-ignoreddebugA PATCH operation targeted an attribute that is accepted but not stored
scim.bulk-processeddebugA bulk request was processed
scim.bulk-abortedwarnA bulk request stopped after reaching its failOnErrors limit
scim.rollback-failederrorA change that failed part-way could not be fully undone, so the group and its members need checking

The status code that the identity provider logs leads to the matching event. A 403 response corresponds to a scim.forbidden event, a 409 to scim.conflict, and a 400 to scim.error. Each event carries the same detail that was returned to the client. The event reference gives the fields of each event.