SCIM provisioning
Let your identity provider create, update and suspend QueryProxy users and sync groups over SCIM 2.0. Tokens, what is supported, how groups become team roles.
SCIM 2.0 lets your identity provider manage QueryProxy users on its own: it creates a user when someone joins, updates the profile, suspends the account when someone leaves, and keeps group membership in step. It complements single sign-on: sign-on decides who may get in now, while SCIM takes access away between sign-ins. Without it, a person removed at the provider keeps any open QueryProxy session until it ends.
SCIM is off by default. While it is off, every path under /scim/v2 answers
404.
Turn it on
QUERYPROXY_SCIM_ENABLED=true
Restart the containers after changing it. Then point the provider at:
https://queryproxy.example.com/scim/v2
That is APP_URL plus /scim/v2. QueryProxy answers with
application/scim+json and reports its abilities at
/scim/v2/ServiceProviderConfig, with /scim/v2/Schemas and
/scim/v2/ResourceTypes next to it.
SCIM tokens
The provider authenticates with a bearer token made for SCIM. It is not a personal access token and is created and revoked separately.
Create one in the web UI, as a system administrator, under Administration →
SCIM (/settings/scim). The page shows the endpoint URL, lists the tokens
(name, who created it, last use, status), creates and revokes them. The token
value is shown once, and only after you confirm your identity again with your
password, a TOTP code or, for an SSO-only administrator, an SSO sign-in within
the last 15 minutes, the same rule as for API tokens. An SSO-only administrator
who is sent to the provider for that confirmation lands back on the SCIM page.
While SCIM is off, the page explains how to turn it on; existing tokens stay
listed and can still be revoked, so a leaked token can be closed at any time.
Or use the command line:
php artisan queryproxy:scim-token create --name=entra --by=admin@example.com
php artisan queryproxy:scim-token list
php artisan queryproxy:scim-token revoke --id=3 --by=admin@example.com
--by is the administrator’s e-mail, for the audit log. A token starts with
qpscim_. QueryProxy stores only its SHA-256 hash, so a lost token cannot be
shown again: revoke it and create a new one.
A request without a valid token gets 401 with a SCIM error body. See
Rate limits for what repeated failures do.
What is supported
| Resources | Users and Groups |
| Operations | GET (one and list), POST, PUT, PATCH, DELETE |
| Filter | eq on userName, externalId, id and emails.value for users; on displayName, externalId and id for groups |
| Pagination | startIndex and count, up to 200 resources per page |
| Not supported | bulk operations, sorting, ETags, password changes |
Errors use the SCIM Error schema. An unexpected failure answers 500 in that
schema and leaves the internal reason out of the body.
Users
- Create.
POST /Usersmakes an account without a password. The user signs in through SSO; on the first sign-in QueryProxy links the account to the provider by e-mail address, if the provider says the address is verified (see first sign-in and linking). IfuserNameis not an e-mail address, as with an Entra ID user principal name, the primary address inemailsis the identity anduserNameis kept for matching. - Update.
PUTandPATCHchange the name, the e-mail address and the active flag. APUTthat leaves outactivekeeps the current state. Paths QueryProxy has no use for (a job title, the enterprise extension) are ignored rather than refused, because providers send them on every sync. A value that is wrong for a path it does know gets400withinvalidValue. The strings"True"and"False", which Entra ID sends for booleans, are accepted. - Suspend, never delete.
active=falseandDELETEsuspend the user: open sessions and personal access tokens stop working at once and the account cannot sign in. Nothing is deleted. After aDELETEthe user still exists and reads back withactive=false. - Lift a suspension.
active=truelifts only a suspension SCIM itself set. If an administrator suspended the account by hand, SCIM leaves it suspended and writes no audit entry. If an administrator suspends an account SCIM already suspended, the administrator takes it over, and SCIM can no longer lift it. - Names must be free. A
userNameor an e-mail address that already belongs to another account answers409withuniqueness.
SCIM manages only the users it created and the users already linked to the
provider through SSO. Local accounts, including break-glass administrators, are
invisible to it: GET, PATCH and DELETE on them answer 404, and creating a
user with the same e-mail address answers 409. A stolen SCIM token therefore
cannot suspend or rename the account you keep for emergencies. SCIM never sets
the system administrator flag either.
An account SCIM created counts as an SSO account. It gets no password through the reset link, and the administrator’s Reset password action refuses it. See Single sign-on.
Groups and team roles
Groups are stored in QueryProxy, with their members. They turn into team roles
through the same QUERYPROXY_OIDC_ROLE_MAP that single sign-on uses; see
the role map. The map is the one place that says
which provider group means which role in which team.
A SCIM group matches a role-map entry by its displayName or its
externalId. Which one carries the value you wrote depends on the provider:
- Entra ID. Microsoft’s SCIM reference maps the group’s
objectIdtoexternalId(see Microsoft’s SCIM guide), and Entra ID puts object IDs in thegroupsclaim, so the role map is written with the object ID:1f9c0c2e-0000-0000-0000-000000000000:payments:dba. Check your enterprise application’s group attribute mapping before you rely on it. - A provider that sends the group name as
displayNamematches an entry written with the name:platform-dba:payments:dba.
What happens when membership changes:
- QueryProxy recomputes the roles of the affected user only in the teams named by the role-map entries that match the changed group. Roles in other teams, including ones given at sign-in or by hand, are not touched.
- In a team, the user’s role is the highest one among all their SCIM groups
(
dba, thendeveloper, thenauditor). - A group that no entry matches is accepted and has no effect on roles.
- Renaming a group, or changing its
externalId, recomputes every member. That runs inside the request, so renaming a group of thousands of members is slow. - Every role change is written as
user.role_synced, withviaset toscim.
SCIM and sign-in both write roles. If the provider reports different groups in the ID token than over SCIM, whichever wrote last wins, so keep both sources consistent. Without a role map, SCIM can create and suspend users but gives no team roles.
Rate limits
QUERYPROXY_SCIM_RATE_LIMIT (default 300) is the request budget of each token
per minute. It is also the number of failed authentications one address may make
per minute: past it, the address gets 429 with Retry-After instead of 401.
| Variable | Default | Meaning |
|---|---|---|
QUERYPROXY_SCIM_ENABLED |
false |
Serve /scim/v2; while off every SCIM path answers 404 |
QUERYPROXY_SCIM_RATE_LIMIT |
300 |
Requests per minute for each token, and failed authentications per address |
In the audit log
| Event | When |
|---|---|
scim.user_provisioned |
A user was created |
scim.user_updated |
A user’s details changed |
scim.user_suspended |
active=false or DELETE |
scim.user_unsuspended |
SCIM lifted its own suspension |
scim.group_synced |
A group or its members changed |
scim.token_created, scim.token_revoked |
A SCIM token was created or revoked |
Upgrading
Run the migrations as usual. They add the SCIM tables (scim_tokens,
scim_groups, scim_group_user) and four columns on users; all can be rolled
back. Nothing changes until you set QUERYPROXY_SCIM_ENABLED=true.
Next steps
- Single sign-on for the sign-in side and the role map.
- REST API and tokens for the other kind of token.
- Audit chain and SIEM export for sending the events on.