QueryProxy
Documentation menu

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 /Users makes 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). If userName is not an e-mail address, as with an Entra ID user principal name, the primary address in emails is the identity and userName is kept for matching.
  • Update. PUT and PATCH change the name, the e-mail address and the active flag. A PUT that leaves out active keeps 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 gets 400 with invalidValue. The strings "True" and "False", which Entra ID sends for booleans, are accepted.
  • Suspend, never delete. active=false and DELETE suspend the user: open sessions and personal access tokens stop working at once and the account cannot sign in. Nothing is deleted. After a DELETE the user still exists and reads back with active=false.
  • Lift a suspension. active=true lifts 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 userName or an e-mail address that already belongs to another account answers 409 with uniqueness.

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 objectId to externalId (see Microsoft’s SCIM guide), and Entra ID puts object IDs in the groups claim, 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 displayName matches 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, then developer, then auditor).
  • 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, with via set to scim.

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

Arrow keys to move, Enter to open.