Session management: terminating and rotating sessions
A console session is the short-lived, signed cookie the engine issues after a successful login, so the browser does not re-run the passkey or identity-provider ceremony on every request. This page is for a self-hoster who needs to respond when something goes wrong with one: a laptop is lost, a phone is stolen, or a team member leaves and their access must end cleanly and now.
There are four ways to terminate sessions, and they differ in scope and in who may run them. The right one depends on whose sessions you need to end and why. This page explains all four actions and the two bounds that limit every session regardless: a twelve-hour absolute cap and a two-hour idle timeout. A later section relates session termination to switching off an identity provider.
A session is a signed cookie with a hard ceiling
A session is a server-signed token of the form body-then-MAC. The MAC is HMAC-SHA-256 over the body, keyed with a signing key the engine generates once and keeps inside its Durable Object. Verification happens inside the Durable Object over a key that never leaves it, and the body is never trusted until its MAC verifies, so a forged or tampered token cannot set an email or extend an expiry.
Alongside each token the engine keeps one record for that session: its sign-in method, when the session began and when it was last used, a coarse browser family, and the sign-in network as an IPv4 /24 or IPv6 /48 prefix. The record never holds the token, the raw address or the user agent. It is what lets you list your own sessions and end one of them; the signed token is still what proves a session (mintSessionRecorded, engine/src/sched/scheduler-do-session.ts).
Two properties of this model matter when you are responding to an incident.
The first is the session lifetime, which has two separate bounds. The outer bound is an absolute expiry set once at mint, twelve hours out, and never slid forward past that ceiling, so a leaked cookie is useless by the next working day no matter what. The tighter bound is an idle timeout: a session that sees no activity for two hours is rejected at the verify gate independent of the twelve-hour cap, so a leaked but unused cookie stops working after roughly two hours of inactivity, well before that next working day. An active session is kept alive by a sliding re-mint that runs about every fifteen minutes and refreshes the session’s last-activity marker, but the re-mint preserves the original issue time and the absolute expiry, so it can never push a session past the twelve-hour cap. The cookie is also HttpOnly so script cannot read it, Secure so it travels only over HTTPS, and SameSite=Strict so it is never sent on a cross-site request.
These two bounds are chosen against NIST SP 800-63B, revision 4. For AAL2 that standard says re-authentication should happen at least every 24 hours and after one hour of inactivity; for AAL3, at least every 12 hours and after 15 minutes of inactivity. The twelve-hour absolute cap meets both levels. The two-hour idle timeout is a deviation from both inactivity figures, and it is deliberate: an operator watches restores, drills and verification runs from the console, and a run of that kind commonly passes an hour with no console interaction, so a one-hour or 15-minute idle bound would force a repeated passkey ceremony in the middle of an operation the operator is waiting on.
The deviation is compensated by controls that are stricter than the idle bound. Every owner-reserved sensitive action needs a passkey session younger than five minutes or a fresh single-use passkey assertion, whatever the session’s age (step-up re-authentication); the role is re-resolved on every request; and a logout, an offboarding or a sign-out of a member’s sessions ends every session for that identity on its next request. Section 2.1a of engine/docs/security/identity-sessions-and-files.md records the same comparison, with the constants and their source lines.
The second is that the role is never carried in the token. The session body holds identity, the verified email and the stable subject the role table keys on, but never the role or the capabilities. The engine re-resolves the role from its own tables on every single request. This is why a demotion or a removal takes effect on the very next request rather than waiting out the cookie: lowering someone’s role immediately changes what their existing session can do.
Re-resolution downgrades, it does not terminate
Re-resolving the role on each request means a demoted member loses the higher capabilities at once. It does not, on its own, end their session. An offboarded member whose role is removed falls back to viewer, which still leaves a read surface live until the cookie expires. That is exactly why offboarding additionally terminates the session, covered below.
Concurrent sessions
A member may hold any number of concurrent sessions, one per browser or device they sign in on. The engine sets no maximum, so there is no maximum-reached behaviour. A sign-in from a browser with no live session cookie evicts nothing. A sign-in from a browser that still presents a live session cookie ends every session of the account that cookie belongs to. The engine bumps that account’s email and subject epochs before it mints the new session. The engine records every session, so you can see them all and end any one of yours, but it does not cap how many you hold.
The way to reduce the count is a termination action, described in the next section. Sign out one session ends the one you choose; sign out my other sessions keeps the session you run it from and ends the rest; sign out a member’s sessions and sign out everyone end every session for their target. A logout and an offboarding bump the epoch as well, so all of that identity’s sessions fail closed on their next request. For a member who signs in through Cloudflare Access rather than a first-party session, any concurrency limit is the Access application’s own setting. The engine can neither read nor enforce it.
The four termination actions
| Action | Scope | Who can run it | Fresh re-authentication needed? | Effect on the actor |
|---|---|---|---|---|
| Sign out one session | One of your own other sessions, chosen from the list | Any member with a first-party session | Yes: a sign-in of any method within five minutes | None; your current session stays alive |
| Sign out my other sessions | Every other session for your own email | Any member with a first-party session | Yes: a sign-in of any method within five minutes | Your current session is re-minted and stays alive |
| Sign out a member’s sessions | All sessions belonging to one named member | An admin holding roles.write | Yes: a fresh passkey | None, unless you terminate your own |
| Sign out everyone | Every session for every member | Owner only | Yes: a fresh passkey | Your other tabs die too; you re-authenticate |
The re-authentication column shows step-up re-authentication, and the engine gates all four actions. The two that reach another member’s session need a passkey session younger than five minutes or a fresh passkey assertion, as other sensitive actions do. The two self-scoped actions accept a sign-in of any method within the last five minutes, or a fresh single-use step-up token, because they reach no session but your own. The engine gates them because a stale session that survived a compromise could otherwise sign the legitimate operator’s other tabs out unchallenged (STEPUP_SUBS, engine/src/admin/router-core.ts). A bare break-glass token session is exempt from the gate; a Cloudflare Access session must have signed in within the last five minutes.
Sign out one session
This is the self-service action for ending one browser you no longer trust while keeping the rest. The access and security screen lists your live sessions with the most recently used first. Each row shows the sign-in method, browser family, sign-in network and last use, and a badge marks the one you are using now. Choose Sign out on another row. The engine records a revocation for that session’s id and refuses the session on its next request; your other sessions keep working. The audit log records the action as session-terminate (terminateOneSession, engine/src/sched/scheduler-do-session.ts).
Sign out my other sessions
This is the self-service action for “I am still here, but sign out everywhere else”, which is what you reach for after losing a second device you were signed in on. The engine bumps your own session epoch, which invalidates every session minted for your email before the bump, then re-mints your current session carrying the new epoch and re-issues that cookie. The result is that your other sessions die on their next request while the tab you are working in stays alive. A bare-token break-glass caller has no first-party session to manage, so it is refused.
Sign out a member’s sessions
This is the admin action for ending one named member’s sessions without touching anyone else, for example when a colleague reports a stolen phone. It is gated on roles.write, so an owner or an access-admin can run it. The engine bumps the target email’s session epoch so all of their email-keyed sessions die on the next request, and for a member who has authenticated through an identity provider it also bumps the per-subject revocation axis, so their identity-provider sessions die too and not only the email-keyed ones. An access-admin can terminate any non-owner member’s sessions, but only an owner may terminate an owner’s sessions.
Sign out everyone
This is the account-wide reset, the in-account equivalent of a global sign-out, for when you suspect the signing key or a session has been compromised. It is owner-only.
Sign-out-everyone signs you out too
Sign-out-everyone does not leave the person who runs it signed in. It works by deleting and rotating the session signing key, so every outstanding token fails its next MAC verify, including the owner’s own other tabs. A fresh key is generated on its next use. Your recovery codes are not affected: the codes are verified under their own recovery key, a separate record from the session signing key, and that key is materialised before the session key is deleted, so a printed recovery sheet keeps working across a sign-out-everyone, which is the break-glass an owner needs if the compromise they are responding to is real. Do not regenerate your codes after running it: regenerating replaces the working set. Expect to re-authenticate immediately after running it, and expect everyone else to as well. This is the point of the action: it ends every session at once rather than chasing them member by member.
Because it rotates the one signing key for the whole account, signing everyone out is reserved to an owner. The engine re-resolves the caller’s role and requires owner, on top of the roles.write gate, so an access-admin cannot sign the whole account out.
Logout and offboarding fail a session closed on the next request
Two everyday events also end sessions. Both work by bumping the session epoch so the affected tokens fail closed at the verify gate, rather than relying on a client-side cookie clear.
An explicit logout is a real server-side termination, not merely a cookie clear in the browser. The engine resolves the identity from the presented token, then bumps the per-email epoch and the per-subject not-before instant. The just-logged-out token, and any copy of it that was exfiltrated, then fails closed on its next request. The browser also drops the cookie, but the server-side bump is what makes the logout meaningful against a stolen copy.
Offboarding a member, removing their role entirely, terminates their sessions for the same reason. Removing the role only downgrades the member to viewer on the next request, which would leave a read surface live for up to the 12-hour ceiling. So offboarding also bumps the per-email epoch and, for a bound member, the per-subject not-before instant, so the next request from any of their sessions fails closed at the verify-time gate.
Ending a session is not the same as ending a way in. A role removal terminates what the person holds now; it leaves the passkey credential they enrolled, any recovery codes they banked and any unredeemed invite in their name untouched, so they can authenticate again and open a session the epoch bump has no quarrel with. Revoking those is a separate act with its own guards, on the Roles and access tab under Sign-in factors, and on the admin API as POST /admin/signin-factors/revoke. Offboard a member is where it and the reasoning for keeping it separate live.
Why this matters (ASVS V7.4.2)
This behaviour is the engine’s answer to ASVS 5.0 V7.4.2: a logout or an offboarding must terminate the associated sessions, so a removed or exfiltrated session does not stay valid for the rest of the 12-hour lifetime. The epoch bump is what makes a removed person’s session, or a copied cookie, fail on the next request rather than living on until it expires. downpipes self-assesses against OWASP ASVS 5.0 at the Level 2 / Level 3-equivalent bar for its authentication component; no product makes you compliant on its own.
How this composes with switching off an identity provider
Session termination and identity-provider revocation are independent mechanisms that compose. The session epoch axes above govern sessions by email and by subject. There is a separate axis keyed on the identity-provider connection, the idpEpoch. Disabling or deleting an identity-provider connection stamps that connection’s epoch, which independently kills every live session minted through that connection on its next request. The verifier additionally re-checks at verify time that the connection still exists and is still enabled.
This means you have two clean ways to cut access for someone signing in through SSO. You can terminate their sessions by member, which bumps their email and subject axes, or you can disable or delete the whole connection, which bumps the connection axis for everyone who signs in through it. The two are described from the SSO side in test, troubleshoot and revoke SSO.
There is one more property worth knowing for an identity-provider session. The member’s groups are snapshotted per subject at sign-in and re-read on every request, and are never carried in the session cookie. So a group change, or a deprovision at the identity provider, takes effect on the next request without waiting out the 12-hour cookie. A role change works exactly the same way. The cookie carries identity, never groups and never the role.
How the engine session relates to your identity provider’s session
When a member signs in through an identity provider, two sessions exist: the provider’s own, and the engine session minted at the end of the callback. They are coordinated in one direction only, and the rules differ by protocol.
The maximum time between authentication events at your identity provider is the twelve-hour absolute cap. The engine holds no refresh token and runs no silent re-authentication, so after twelve hours, or after two hours idle, the member signs in at the identity provider again. A termination action, a logout, an offboarding, or disabling the connection ends the engine session sooner, as described above.
For a SAML connection, the engine reads the assertion’s AuthnStatement SessionNotOnOrAfter attribute when the provider sends one, and caps the session at the earlier of that instant and the twelve-hour cap, so the engine session never outlives the provider’s session (sessionNotOnOrAfter in engine/src/admin/saml/assertion.ts, applied by signSession in engine/src/admin/session.ts as the minimum of the engine cap and the provider bound). A value the engine cannot parse is read as no bound and is counted in the authentication signals as sso-session-cap-dropped, so a provider sending an unreadable bound is visible in the support pack rather than silently extending the session.
For an OIDC or OAuth2 connection the engine consumes no provider session bound. It does not read OIDC session management or back-channel logout, and it does not cap the session at the ID token’s expiry (nativeSessionIssue in engine/src/sched/scheduler-do-idp.ts passes no bound for an OIDC sign-in). A session end on the provider’s side therefore takes effect at the twelve-hour cap, the two-hour idle bound, a termination action for that member, or the connection’s idpEpoch, whichever comes first.
There is no single logout in either direction. A sign-out at the provider does not end an engine session, and an engine logout does not end the provider’s session. Offboarding a member who signs in through a provider is therefore two acts: deprovision them at the provider, and terminate their sessions here. Doing only the first leaves an engine session live until a bound or an axis catches it; doing only the second lets them sign in again while the provider still admits them.
Where the actions live
The console surfaces all four actions on its access and security screen, calling the engine routes below. The engine is always the enforcement point; the console only mirrors its rules so a button is never a dead end.
| Action | Engine route | Console |
|---|---|---|
| Sign out one session | GET /sessions lists them, POST /sessions/terminate ends one by id | A Sign out control on each of your other sessions; step-up gated |
| Sign out my other sessions | POST /sessions/terminate-others | A self-service control; the new cookie is re-issued in the response; step-up gated |
| Sign out a member’s sessions | POST /sessions/terminate-user | A member email field and a Sign out this member button, shown to a roles.write holder; step-up gated |
| Sign out everyone | POST /sessions/terminate-all | An owner-only control; step-up gated |
Where this fits
- Test, troubleshoot and revoke SSO covers disabling or deleting an identity-provider connection, the independent
idpEpochaxis that kills that connection’s sessions. - Offboard a member is the end-to-end departure runbook, of which the session termination here is one step.
- Roles and capabilities defines
roles.writeand confirms that the role is re-resolved per request rather than baked into a session. - Account compromise eviction is the broader incident response, where terminate-all is one lever among several.
The config-version signing key coupling
The session signing key the engine rotates on a terminate-all is the same in-Durable-Object key that signs the config version history. A useful consequence to be aware of: a terminate-all rotates that key, which also invalidates verification of any config-version digest recorded before the rotation, because those versions were signed with the old key. After a terminate-all, treat the verify verdict for config versions captured before that point as reset rather than as evidence that anything was tampered. This is a side effect of one shared key, not a flaw. The change control and config-version-history pages describe the config side of the same key.
Last updated .