Skip to content
downpipes docs

SCIM, deprovisioning, and the joiner-mover-leaver scope

If you run access from an identity provider, you will ask the lifecycle question: when someone joins, moves team, or leaves, what does downpipes do, and what is still yours to do in your directory. This page answers that for the current release, including the parts downpipes does not do, so you can plan the gap rather than discover it.

The short version is that a leaver’s role can be removed by your provider’s SCIM connector through a minimal SCIM facade, or by an in-product ceremony, that in both cases the ways they sign in survive it and are revoked separately, and the joiner and mover cases are covered by mapping your directory groups to roles. The SCIM surface downpipes exposes is deliberately narrow: it deprovisions a leaver and does nothing else. It provisions no one and reads no user records. The rest of this page is precise about where that line sits and what stands in for the parts beyond it.

What this release exposes, in one line

downpipes exposes a minimal SCIM 2.0 facade your identity provider can target for one job: removing a leaver’s role, by DELETE /scim/v2/Users/{id} or PATCH /scim/v2/Users/{id} with active:false. Joiner and mover are handled by group-to-role mapping that follows your directory on the next sign-in. Removing the person in your directory, ending their Cloudflare Access session, and revoking the passkey, recovery codes and invites they hold in the engine all stay yours to do. SCIM provisioning, reads, and reactivation are out of scope by design.

Joiner and mover: group-to-role mapping follows your directory

You do not provision a joiner into downpipes one person at a time, and you do not re-grant a mover. Instead, map your identity-provider groups to downpipes roles once, and a person’s role follows the groups their verified sign-in carries.

A joiner who lands in a mapped group gets the mapped role on their first sign-in, with no second grant to maintain in downpipes. A mover whose group membership changes in your directory resolves to the new role on their next request, because the engine re-reads the verified groups per request rather than trusting a cached session. The mapping is setGroupRole in the scheduler Durable Object, gated on the access.policy capability, and a group can map to any role except owner. The mechanics, the per-provider claim extraction, and the honest limits live on group-to-role mapping.

This is the recommended way to run joiner and mover at any size, because your directory stays the single place you manage who is in which team, and downpipes follows it. The one role it cannot drive this way is owner, which stays an explicit, named, per-person grant.

Leaver over SCIM: the deprovision facade

If your identity provider runs a SCIM connector, point its deprovision at the downpipes SCIM facade and a leaver is offboarded automatically. The facade lives under /scim/v2 and is dispatched from the Worker entry before the console admin branch, so it has its own authentication and never touches the console auth model (handleScim in engine/src/admin/scim.ts, wired in engine/src/index.ts).

The facade is deprovision-only by design. It drives the same audited offboarding that the member-removal ceremony drives: it removes the person’s in-app role and bumps the engine’s per-email session epoch and per-subject not-before instant, so any session they hold fails closed on the next request. It provisions no one, reads no user records, and supports no other write.

A SCIM deprovision does not revoke the ways the person signs in

The facade forwards to the engine’s /roles/delete, the same route the console ceremony calls (engine/src/admin/scim.ts). Role deletion removes the person’s privilege and fails their live sessions closed. It does not touch the three stores the engine keeps a way in under, so a person your connector has deprovisioned can still authenticate and come back holding whatever role the account then resolves for them. Revoking those is a separate, deliberate act, and no SCIM connector can reach it: the facade’s own scope is a single route. Somebody has to do it, on the Roles and access tab under Sign-in factors, or on the admin API as POST /admin/signin-factors/revoke. Wiring SCIM automates the role half of a leaver and no more. See offboard a member for the three stores, the revoke and its guards.

The two requests that offboard a leaver

{id} in each path is the member’s email, URL-encoded. Configure your connector so the SCIM userName or externalId is the person’s email, the same email they sign in with.

Request Effect
DELETE /scim/v2/Users/{id} Offboards the member. Returns 204 No Content. Removing an already-absent member is a safe, idempotent no-op and still returns 204.
PATCH /scim/v2/Users/{id} with a SCIM PatchOp setting active to false The same offboarding as DELETE. Returns 204 No Content. Both the path-scoped form ({op:"replace",path:"active",value:false}) and the value-object form ({op:"replace",value:{active:false}}) are accepted, because different connectors emit each.

A connector that probes the surface gets honest answers for the two reads it makes. GET /scim/v2/ServiceProviderConfig advertises the real capability: PATCH is supported, and bulk, filter, sort, changePassword, and ETag are not. GET /scim/v2/Users returns an empty ListResponse, because this facade does not enumerate your members and discloses no member data. Any other route or method returns a SCIM error.

What the facade does not do, and why

Everything beyond deprovision is out of scope, and the surface says so rather than pretending otherwise.

SCIM operation Supported? Why
DELETE /Users/{id} (deprovision) Yes Drives the existing audited offboarding: role removal plus session termination, and not sign-in-factor revocation.
PATCH /Users/{id} with active:false (deactivate) Yes The same offboarding as DELETE.
PATCH /Users/{id} with active:true (reactivate) No Rejected with a SCIM mutability error rather than silently accepted, so the provider is never told a reactivation succeeded that did not happen. Provisioning happens through a console role grant plus the person’s next sign-in, not here.
POST or PUT /Users (create or replace) No This facade provisions nothing. A joiner gets a role through group-to-role mapping or an explicit grant.
GET /Users/{id} (read a member) No The facade reads no user records and discloses no member data.
Bulk, filter, sort, changePassword, ETag No Advertised as unsupported in ServiceProviderConfig so a conformant connector plans around them.

Authentication and fail-closed behaviour

The facade is gated by a dedicated bearer, SCIM_BEARER_TOKEN, separate from the owner break-glass ADMIN_TOKEN on purpose: your SCIM connector holds only a leaver-offboarding credential, never the owner break-glass.

When SCIM_BEARER_TOKEN is unset, the whole surface is off and every /scim/v2 request returns 503, so a deployment that has not opted in has no SCIM attack surface at all. When it is set, the presented Authorization: Bearer value is compared against it in constant time over the SHA-384 digest of each side, so neither the value nor its length leaks, and a per-IP rate limit, the same fail-closed brute-force gate the unauthenticated admin ceremony uses, fronts the check so a stolen-endpoint guess is throttled and a limiter outage denies rather than opens.

A removed in-app role is not a closed door, at the edge or inside the engine

A SCIM deprovision, like the member-removal ceremony, ends the person’s downpipes privilege and fails their engine sessions closed on the next request. Two things stand between that and a closed door, and the deprovision reaches neither. Outward, it does not end their Cloudflare Access session and does not remove them from your identity provider, because the engine holds no credential for either. Inward, it leaves their passkey credential, their recovery codes and any unredeemed invite in place, each of which is a fresh way back in that the edge never sees, because these are the engine’s own stores and sit behind Access rather than in front of it. A leaver is closed when the directory removal, the Access session end and the sign-in-factor revoke have all been done. See offboard a member for the boundary in full.

Leaver in the console: the member-removal ceremony

If you do not wire a SCIM connector, or you want to offboard someone by hand, the member-removal ceremony on the Access and security screen does the same engine-side revocation: it removes the person’s in-app role immediately and audibly, and bumps the session epoch and not-before instant so their sessions fail closed on the next request. The facade and the ceremony reach the same audited offboarding by different front doors. The full flow, including the last-Owner guard and that removing an absent member is a safe no-op, is on offboard a member.

If a group-to-role mapping was the only thing granting the person a role, removing them from that group in your directory also drops their downpipes role on their next sign-in, because the role is re-resolved from the verified groups each time. That is the mapping working as a deprovision path for a group-granted role; an explicit per-email grant is removed by the SCIM facade or the member-removal ceremony instead.

The honest scope, in one table

State the scope plainly so it can be planned against.

Lifecycle event Covered today? How
Joiner Yes, via the directory Land them in a mapped group; the mapped role applies on their first sign-in (setGroupRole).
Mover Yes, via the directory Change their group in your directory; the new role resolves on their next request (groups re-read per request).
Leaver, over SCIM Yes, deprovision-only DELETE /scim/v2/Users/{id} or PATCH with active:false drives the audited offboarding.
Leaver, in the console Yes The member-removal ceremony removes the in-app role and fails their sessions closed on the next request.
Leaver, in your directory and at the edge No, by design The engine holds no credential for your identity provider or Cloudflare Access, so removing the person there is yours to do.
Leaver, their ways of signing in to the engine Yes, but not from here POST /admin/signin-factors/revoke clears the passkey credential, the recovery codes and any live invite for one email as one audited act. The SCIM facade does not reach it: a connector-driven deprovision removes the role and nothing else. The console reaches it, on the Roles and access tab under Sign-in factors, so this is a step somebody has to take deliberately rather than one your directory completes.
SCIM create, update, reactivate, or read No, by design The facade is deprovision-only. It provisions no one and reads no user records.

The reason the SCIM surface is deprovision-only is the same no-custody constraint that runs through the product: a downpipes deployment holds your account’s own data and never an identity-provider secret. Group-to-role mapping reaches the joiner and mover outcome by reading the groups your provider already signs into the token, which needs no standing credential, and the SCIM facade reaches the leaver outcome with only a leaver-offboarding bearer, so neither needs the broad standing access that full SCIM provisioning would.

The scim-users and scim-groups names you may see in config backup are a different thing

The Cloudflare configuration backup includes surfaces named scim-users and scim-groups. Those read your account’s SCIM provisioning records from Cloudflare for backup, recorded from cf-config-surfaces.ts. They are part of capturing your Cloudflare configuration, not the SCIM facade downpipes runs for your identity provider to deprovision into. Backing up those records and exposing the inbound deprovision endpoint are unrelated capabilities.

Wiring an Okta or Microsoft Entra ID connector

Okta and Microsoft Entra ID both push lifecycle events to an application over SCIM by pointing a connector at a base URL with a bearer token and enabling event classes such as Create, Update, and Deactivate or Deprovision.

Point the connector’s SCIM base URL at /scim/v2 on your deployment, issue it the dedicated SCIM_BEARER_TOKEN, and enable only the deactivate or deprovision event class. Plan the sign-in-factor revoke as a step your connector does not cover: a deprovision event drops the role, and the passkey, recovery codes and any live invite for that address survive it. The create, update, and reactivate classes have no effect here by design: a reactivate is refused with a SCIM mutability error, and create or read routes return a SCIM error, so enabling them only produces connector errors rather than silent half-success. Configure the joiner and mover side with group-to-role mapping instead, so a person’s role follows the groups your provider already sends in the sign-in token.

  • Group-to-role mapping is the canonical home for the joiner and mover path, the per-provider group extraction, and the limits including that no group can confer owner.
  • Offboard a member is the leaver ceremony in full, including the hard boundary that downpipes cannot deprovision your identity provider or end a Cloudflare Access session.
  • Session management explains the session epoch and not-before instants that make a removal take effect on the next request.
  • Supported providers lists the providers downpipes ships presets for and what each emits in the token.

Last updated .