Offboarding a member and the IdP boundary
Offboarding a member in downpipes is three jobs, and only one of them happens in the console today. The console removes the departing person’s in-app role immediately and records that you did the rest. Removing the ways they can still sign in to the engine is a separate, deliberate action. Ending their identity-provider access and their Cloudflare Access session at the edge lives outside the console entirely and is yours to do.
This page is for the self-hoster who is removing someone from the team. It walks the member-removal ceremony on the Access and security screen, sets out the three sign-in stores a role removal does not touch, explains the confirmation that records your IdP cleanup as an audit intent, and states the hard boundary plainly so you do not mistake a removed in-app role for a closed door.
The member-removal ceremony
There is one removal flow, reached by the Remove action on the member’s row on the Roles and access tab. Every other entry point links here, so the same confirmation is always offered and the same record is always written (offboardingLinkCard points at the single ceremony, console/src/screens/access-security/fallback.ts).
When you confirm, the console removes the person’s in-app role through deleteRole, which is immediate and audited (openOffboardModal, console/src/screens/access-security/roles-members.ts; deleteRole, console/src/api.ts). The engine is the authority for this: it removes the grant, refuses to remove the last Owner, and records the removal in the tamper-evident audit log. The flow is safe to repeat against someone who has already gone, and repeating it is not inert: an offboard of an address the role table cannot see still ends that address’s live sessions and still writes an audit entry, for the reason the next section sets out.
Role removal also bumps the engine’s own session epoch for that person, so their next request from any engine session fails closed rather than running on for the rest of the session lifetime. This is an engine-side revocation on the axes the engine controls. It does not reach the edge, which is the boundary the next section is about.
Removing the in-app role is the part the console can do, and it does it at once. It strips the person’s privilege on the engine and bumps the engine session epoch so their engine sessions stop working on the next request. It does not, and cannot, end a Cloudflare Access session at the edge.
Authority that arrives through a group is not removed by Remove
A member’s effective role is the stronger of two sources: the role row held against their email address, and the highest role mapped to any group their identity provider asserts in their sign-in claim (resolveRole, engine/src/sched/scheduler-do-rbac.ts). A group-mapped grant needs no row of any kind. So a member whose authority comes only from a group claim has no row in the members roster at all, and Remove is not offered against them, because the roster has nothing to show.
Offboard that address anyway, from your directory tooling or by hand, and the engine answers honestly. It reports that nothing was deleted, because no grant existed to delete. What it does do is end their live sessions: it bumps that address’s session epoch and records a session termination in the audit log, so the cookie they are holding stops working on its next request rather than running to its natural expiry.
Ending the session is not the same as removing the authority, and this is the part to plan for. The next time they sign in they resolve to the same role, because the group mapping still confers it. What removes the authority is removing the person from that group in your own directory. Until you do, the engine keeps granting them the role every time they authenticate, and it is right to: the claim it is handed still says they hold the group.
Revoking sign-in factors does not close it either. A member who signs in through a group claim usually holds no passkey credential, no banked recovery codes and no unredeemed invite, so the revoke finds nothing to remove and deliberately does not terminate a session on the strength of a revocation that closed no way in (revokeSignInFactors, engine/src/sched/scheduler-do-signin-factors.ts).
A group-claim member is offboarded in your directory, not here
For a member whose role comes from a group, the console-side offboard ends the session they are holding and nothing more. Removing them from the group in your identity provider is the step that removes the authority. Treat it as part of the same ceremony as the IdP cleanup below, not as a follow-up you can leave for later, because in the meantime a fresh sign-in restores their full role.
Why the offboard does not delete the group mapping
A group-to-role mapping is estate policy about a directory of people, not one person’s grant. Deleting it during an offboard would demote everybody who holds that group rather than the person leaving. So the mapping is deliberately left untouched, and narrowing who holds it is a change you make in your directory or on the mapping itself, never a side effect of removing one member.
A recovery import re-grants the roles the export carried
Rebuilding a wiped control plane from a signed export re-applies every role grant in that export, bound grants by their stable subject and pending ones by email, without checking them against anything else. That is deliberate: the disaster this path exists for is one where the audit chain is gone, so there is nothing left to check the export against, and refusing would leave the estate with no Owner and no way back in.
It cannot resurrect authority on a live estate. Both recovery paths refuse outright unless the control plane and the role table are both empty, and there is no force overwrite. What it does mean is that a member offboarded after that export was taken is granted their role again by the recovery, so re-run the offboard against the recovered estate before you hand it back to the team.
The separate estate import, the one that recovers a definition into a fresh account, is the opposite case and imports no authority at all: roles, group and custom roles, identity-provider connections and the export’s org policy are never applied by it.
Removing the role does not remove the ways they sign in
This is the step to plan for, and it is the one an offboarding is most likely to stop short of. Removing the in-app role removes their authority. It does not remove their credentials, and there are three of those, not one.
The engine keeps every way one person can authenticate in three separate stores: their WebAuthn passkey credentials, their banked recovery codes, and any passkey invite issued to them that has not yet been redeemed or expired. A passkey is the hardest of the three, because it needs the physical authenticator. The other two are bearer secrets, and either one on its own mints a session and leads straight to enrolling a fresh credential: a recovery code returns enrolPasskey: true, and an unredeemed invite enrols a credential with no check against the current roster.
deleteRole touches none of them. So an account can read “no credentials” for a departed member while two live ways in stand in their name.
Read the person's sign-in factors before you call the offboarding done
An offboarding that removes the role and stops there leaves a departing member holding whatever recovery codes and unredeemed invites they already had. Those are enough to sign back in and enrol a new authenticator. Removing them is a separate, deliberate action, described below; do it as part of the same ceremony rather than assuming the role removal covered it.
GET /admin/signin-factors?email=<address> reads what one person still holds: their credential count and ids, whether recovery codes are present and how many are unconsumed, and how many invites are live with the soonest expiry. It reports and never deletes, and it is redaction-safe, so it carries no code, no code hash and no invite token. Omit ?email= and it reads the whole account, which needs roles.write.
POST /admin/signin-factors/revoke with {"email": "<address>"} is the write: it removes all three stores for that address as one audited operation and terminates their sessions. It is roles.write, has no self-service arm (revoking your own every factor is a self-lockout with no legitimate use), and carries the sole-Owner floor and dual control, so it cannot be used to strand the account with no Owner. It is also step-up gated, so a cookie-borne session is asked for a fresh passkey assertion before it runs. Plan an urgent offboarding around that: this route is the one in the engine that destroys recovery codes, which cannot be re-derived, and it needs the authenticator of the person doing the offboarding, not the person being offboarded. Its receipt counts what it removed per store, and it reports live and expired invites separately so a call that swept only dead records cannot read as one that closed a way in.
Why this is not part of Remove member
An email can hold authority from a role row and from an identity-provider group claim at the same time, and the effective role is the strongest of the two. For such a member, removing the role row is a demotion to whatever their group still confers, not a departure. Folding factor destruction into that would mean demoting a colleague who remains a legitimate user also destroyed their authenticator and their recovery codes, and recovery codes cannot be re-derived. Deliberate removal should revoke; a demotion should not; deleteRole cannot tell which one it is being asked to do, so the revoke is told explicitly instead.
Where the control is in the console
Both routes are on the Roles and access tab, under Sign-in factors (who can still sign in). It is a second table rather than a column on the members roster, and the reason is what the roster cannot show: an email whose role row was deleted keeps its passkey credentials, its banked recovery codes and any unexpired invite, so it is not a row in the roster at all. Each row states a verdict, “can sign in”, “no way in” or “cannot be judged”, and an inventory of what that email holds. Revoke sign-in on a row opens a confirmation whose action is Revoke every factor.
Two refusals sit in front of the control. Revoking your own factors is refused by the console before the call, and the refusal says it is the console’s own: the engine does not refuse it, so a roles.write holder who is not the last Owner could otherwise strip themselves with no way back. Revoking an Owner’s factors is disabled for anybody who is not an Owner, which mirrors the engine’s own anti-escalation guard rather than offering a control that fails server-side. An engine build that predates the union read says so in the panel rather than showing an empty table, because an empty table here would read as “nobody can sign in”, which is the false negative this whole surface exists to stop.
The hard boundary: the console cannot deprovision your IdP
State this to yourself plainly before you rely on the removal. The console cannot deprovision your identity provider, and it cannot end the person’s Cloudflare Access session. Both of those live at the edge and inside your own identity provider, which the console never holds a credential for. The console removes the in-app role and records that you did the IdP cleanup; it does not perform it (openOffboardModal copy, console/src/screens/access-security/roles-members.ts).
The reason is the same no-custody constraint that runs through the rest of the product. The console never captures a password, an IdP secret, or a Cloudflare token, so where a step needs one it links you to the Cloudflare dashboard and says so, rather than acting on your behalf (access-security.ts responsibility rows). Cloudflare Access enforces sign-in at the edge in front of both the console and the engine, and ending an Access session is an action there, not a switch in this console.
A removed in-app role does not end a Cloudflare Access session. If the person can still pass your Access policy, they can still reach surfaces the edge lets through until you remove them in your identity provider or end their Access session. The IdP step is necessary, and on an Access-fronted account it is the one that closes the edge; it is not sufficient on its own, because the engine sign-in factors above sit behind the edge and survive both the role removal and the IdP cleanup.
The I-have-removed-this-person confirmation
The removal modal offers a checkbox: “I have removed this person in our IdP / Cloudflare Access.” It is not a control that changes anything at the edge. It is a record that you say you did.
When the checkbox is ticked, the console records an audit intent, the access-policy-change-intent marker, through the one audit write the console is allowed to make (openOffboardModal calls recordAuditIntent("access-policy-change-intent"), console/src/screens/access-security/roles-members.ts; recordAuditIntent, console/src/api.ts). That write is Owner-gated and best-effort: if it fails, the removal itself already succeeded and is audited, and the intent simply does not get its own entry. The modal also links out to the Cloudflare Access users dashboard so you can do the real step in the same flow.
The confirmation marker is best-effort audit intent, not proof. It records that you stated the IdP cleanup was done; the engine cannot witness the out-of-band step, so it never asserts the IdP actually changed. The authoritative record for an edge or IdP change lives in Cloudflare, deploy, and identity-provider logs, not in this marker.
Sign-out semantics, precisely
Signing out is a related but separate action, and its scope is worth stating exactly because it is easy to over-read.
Signing out clears the console’s in-memory session state and asks the engine to terminate the session server-side. The engine does not merely clear the browser cookie: it bumps the identity’s session epoch and per-subject not-before instant, so the just-signed-out token and any exfiltrated copy of it fail closed on their next request. That signs the account out of all its engine sessions, not just one passkey session (logout handler, engine/src/admin/router-auth-flow.ts; passkeySessionLogout, engine/src/sched/scheduler-do-session.ts). What it does not do is end the Cloudflare Access session at the edge: the console cannot invalidate an edge session itself, so ending the SSO session happens at Access. Any key files you downloaded during setup also remain on the operator’s disk; signing out is a session action, not a cleanup of local files.

The card states that boundary in place, so the limit is read at the moment of signing out rather than only here. The identity and the protected hostname are masked in this figure by the capture’s redaction policy; in your own console they are your own values.
| Action | What it does | What it does not do |
|---|---|---|
| Remove member (in-app role) | Removes the role row at once, audits it, bumps the engine session epoch | End a Cloudflare Access session at the edge; deprovision the IdP; remove the person’s passkey credentials, banked recovery codes or unredeemed invites; remove authority a group claim confers |
| Offboard an address with no role row (group-claim member) | Ends their live engine sessions and audits the termination | Remove any authority. They resolve to the same group-mapped role on their next sign-in until you remove them from the group in your directory |
| Revoke sign-in factors (Roles and access, Sign-in factors) | Removes all three engine sign-in stores for one email as one audited operation and terminates their sessions | End a Cloudflare Access session at the edge; deprovision the IdP |
| Tick the IdP-cleanup checkbox | Records an access-policy-change-intent audit intent and links out to the Access users dashboard |
Prove or perform the IdP change |
| Sign out | Clears in-memory console state and terminates the engine session server-side (bumps the session epoch and per-subject not-before), signing the account out of all its engine sessions so a captured token also fails closed | End the Cloudflare Access session; delete downloaded key files from disk |
The through-line across the table is that the console acts on what it controls, the in-app role and the engine session, and records, rather than performs, the steps that belong to the edge and your identity provider.
Where this fits
Offboarding one member is part of the larger picture of who can do what and how someone leaves entirely. For the roles you are removing and what each one can do, see roles and capabilities. For the audited intent markers and how out-of-band changes are recorded against the engine, see the audit log. When you are winding the whole product down rather than removing one person, the controlled exit sequence covers removing every role, deprovisioning identity in your own dashboard, and keeping your offline keys; that exit sequence is its own subject and is not the per-member ceremony described here.
Last updated .