Syncing from a SCIM 2.0 endpoint¶
Identity Atlas can pull accounts and group memberships from any SCIM 2.0 service
provider — SAP Cloud Identity Services, Okta, a home-grown SCIM façade, anything
that speaks RFC 7643/7644. There is
no vendor-specific code: the crawler asks the endpoint what it serves
(/ServiceProviderConfig, /ResourceTypes, /Schemas) and reads the standard
/Users and /Groups collections.
This crawler is experimental
It is built and covered by automated tests, but it has had only limited opportunity to prove itself against real SCIM providers. It therefore does not appear in Add Crawler until you switch on Admin → Experimental → Experimental crawlers. Turning that switch back off later leaves a SCIM crawler you already configured running — it only stops new ones being added. See Experimental features.
What Gets Imported¶
| SCIM object | Identity Atlas |
|---|---|
User |
Principals (principalType from your user-type mapping, default User) |
Group |
Resources (resourceType = Group) |
| Group member that is a user | ResourceAssignments (assignmentType = Direct) |
| Group member that is another group | ResourceRelationships (Contains) and a per-user Indirect ResourceAssignment on the outer group |
The SCIM id is preserved as externalId on every record, and the Identity Atlas
primary key is derived from it deterministically — so re-running a sync updates the
same rows instead of creating new ones.
Always-synced core mapping¶
| SCIM attribute | Identity Atlas field |
|---|---|
id |
externalId |
displayName (falls back to userName, then id) |
displayName |
userName |
stored in extendedAttributes |
active |
accountEnabled (active: false → disabled) |
emails — the entry flagged primary, else the first with a value |
email |
name.givenName / name.familyName |
givenName / surname |
title |
jobTitle |
userType |
drives the principal-type mapping (see below) |
Group: id → externalId, displayName → displayName, resourceType = Group.
Extra attributes are opt-in¶
Nothing beyond the core mapping is synced unless you pick it. The wizard's
Attributes step lists every simple attribute the endpoint declares in
/Schemas (complex attributes contribute their simple sub-attributes as
name.middleName; multi-valued attributes are not offered in v1), starting with
nothing selected and a Select all action per object type. Selected attributes
land in the record's extendedAttributes JSON.
Attributes that come from a schema extension are offered — and stored — under their plain name, not the extension URN. In the resource JSON a compliant provider nests them under the extension's URN (RFC 7643 §3.3):
{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group",
"urn:example:params:scim:schemas:extension:acme:2.0:Group"],
"id": "g-1", "displayName": "Admins",
"urn:example:params:scim:schemas:extension:acme:2.0:Group": { "type": "security" } }
Selecting type there stores type: "security" — the crawler looks inside every
extension the resource carries. This matters most for groups: the core Group schema
declares only displayName and members, so every attribute in the group picker
comes from an extension. If two schemas declare the same attribute name, the base
schema's value wins, then extensions in the order the provider lists them.
Prerequisites¶
- A SCIM 2.0 endpoint that serves
/Usersand/GroupswithstartIndex/countpaging - Credentials for one of the supported auth methods (below)
- The endpoint must be reachable from the Identity Atlas worker container
Running a Sync¶
Via the UI (recommended)¶
- Navigate to Admin → Crawlers
- Click Add Crawler and select SCIM 2.0
- Work through the six steps: connection → credentials → objects → attributes → type mapping → schedule
- Click Add Crawler, then Run now
Steps 3 and 4 run live discovery against the endpoint, which doubles as a credential check: a failure is reported inline with the reason.
Via the command line¶
.\tools\crawlers\scim\Start-ScimCrawler.ps1 `
-ApiBaseUrl "http://localhost:3001/api" `
-ApiKey "fgc_abc123..." `
-JobId 0 `
-ConfigPath ".\myconfig.json"
Configuration¶
| Field | Required | Description |
|---|---|---|
baseUrl |
Yes | The URL that serves /Users and /Groups, e.g. https://api.example.com/scim/v2 |
authMethod |
Yes | One of BasicAuth, ApiToken, OAuth2CC |
username / password |
BasicAuth | HTTP Basic credentials |
apiToken |
ApiToken | Static bearer token |
tokenEndpoint / clientId / clientSecret |
OAuth2CC | OAuth2 client-credentials grant |
scope |
No | Optional OAuth2 scope requested with the client-credentials grant |
allowPrivateNetwork |
No (default false) |
Allow baseUrl / tokenEndpoint to resolve to a private or loopback address — set this for a server on your own network. See Network access |
allowInsecureHttp |
No (default false) |
Allow plain http for baseUrl / tokenEndpoint. Credentials are then sent unencrypted. See Network access |
systemName |
No | Overrides how this source is labelled in Identity Atlas. Omit it and the system is named after the crawler itself (SCIM only when the crawler has no name) — see System naming |
pageSize |
No (default 100) | The SCIM count parameter |
selectedObjects |
No | { users, groups, groupMembers } booleans — all default to true |
selectedAttributes |
No | { user: [...], group: [...] } — the opt-in extras, empty by default |
userTypeMapping |
No | Rows of { userType, principalType }; a blank userType is the catch-all |
Secrets (password, apiToken, clientSecret) are never stored in the config blob
— they go to the secrets vault and are injected into the job at dispatch time.
System naming¶
The Identity Atlas system this crawler registers is named after the crawler itself. Name the crawler SAP CIS and the Systems list shows SAP CIS — so several SCIM crawlers side by side stay distinguishable. Rename the crawler and the system follows on its next run (the system is keyed on its base URL, so the row is updated, not duplicated).
Fill in the wizard's optional System name field only to label the system as something other
than the crawler — it is an override and always wins. Leaving it blank is not a choice of the
literal SCIM: that name is used only when the crawler has no name at all.
A crawler configured before this behaviour existed has the literal SCIM stored in its System
name field, because the older wizard wrote it there whenever the field was left blank. A stored
value of exactly SCIM is therefore treated as "not set" and the crawler's own name is used — no
re-saving needed. Any other value, including SCIM Test, is kept as the override it looks like.
Network access¶
A SaaS SCIM endpoint on public https needs no extra setting. For a SCIM service provider inside
your network, tick Allow private network in the wizard (allowPrivateNetwork: true); for
one without TLS, Allow insecure HTTP (allowInsecureHttp: true). Both also cover the OAuth2
tokenEndpoint. See Crawler URL rejected
for exactly what is checked and when.
Example¶
{
"baseUrl": "https://cis.example.com/scim/v2",
"authMethod": "OAuth2CC",
"tokenEndpoint": "https://cis.example.com/oauth/token",
"clientId": "identity-atlas",
"systemName": "SAP CIS",
"pageSize": 100,
"selectedObjects": { "users": true, "groups": true, "groupMembers": true },
"selectedAttributes": { "user": ["department", "costCenter"], "group": ["description"] },
"userTypeMapping": [
{ "userType": "technical", "principalType": "ServicePrincipal" },
{ "userType": "", "principalType": "User" }
]
}
Mapping user types to principal types¶
SCIM's userType is a free-form string, so the crawler maps it onto the Identity
Atlas principal-type vocabulary. Matching is case-insensitive and exact; a row with
a blank userType is the catch-all; with no match and no catch-all an account is a
plain User. Valid principal types are User, ServicePrincipal,
ManagedIdentity, WorkloadIdentity, AIAgent, ExternalUser and SharedMailbox.
Scheduling and sync mode¶
Schedules work exactly as they do for every pull crawler. SCIM 2.0 defines no standard change feed or watermark, so there is no real delta sync: if a schedule (or a manual run) asks for a delta, the crawler executes a full sync and says so in the job log. Full syncs are idempotent, so this is safe to run on any cadence your endpoint can serve.
What a full sync deletes¶
A full sync reconciles: rows this crawler previously wrote that the endpoint no
longer serves are deleted. The delete is scoped to this crawler's own system — and,
for principals, additionally partitioned by mapped principalType — so it can never
touch another connector's data, and a ServicePrincipal batch can never delete the
User rows.
Limitations (v1)¶
- Read-only. No SCIM write-back, no
/Bulk, noPATCH. - Users and groups only. Other resource types the endpoint serves are listed in the wizard as visible but not yet syncable.
- No delta sync (see above).
- No mTLS / X.509 client-certificate auth, and no OAuth2 resource-owner password grant.
- Simple attributes only in the opt-in picker — multi-valued attributes other
than
emails(which is part of the core mapping) are not stored. - No Identities. SCIM supplies accounts; the scheduled Account Linking engine correlates them into identities afterwards, exactly as for every other account source.
Troubleshooting¶
| Symptom | Cause / fix |
|---|---|
| Discovery says Could not reach the SCIM endpoint: /ResourceTypes returned HTTP 401 | Wrong credentials, or the endpoint expects a different auth scheme than the one selected. |
| Save, discovery or the job says baseUrl rejected / tokenEndpoint rejected | The URL uses http or resolves to a private, loopback or cloud-metadata address. Enable Allow private network / Allow insecure HTTP for an on-premises endpoint — see Network access. Metadata and link-local addresses cannot be enabled. |
| Discovery says endpoint answered with a redirect | The wizard does not follow redirects. Enter the URL the endpoint redirects to. |
| The attribute picker is empty | The endpoint does not serve /Schemas, or its schemas declare no simple attributes beyond the core mapping. The sync still works — only the opt-in extras are unavailable. |
| A selected attribute is discovered but never gets a value | Check the raw resource JSON from GET /Users or GET /Groups. If the attribute only appears on GET /<collection>/{id} it is declared returned: "request" and the list response omits it — this crawler reads the list endpoints only. An attribute nested under a schema-extension URN is read (see Extra attributes are opt-in); one served only per-resource is not yet. |
| Group members are missing | Members whose id matches neither a synced user nor a synced group are skipped and counted; the job log reports how many. That usually means the group contains a resource type this crawler does not sync yet. |
| Users appear but no memberships | Check that Group members is enabled in the wizard's Objects step, and that the endpoint returns members on /Groups (some providers require an explicit attribute request). |