BuilderProof editorial team14 min read35 views

Can You Actually Log Someone Out? A Session Lifetime and Revocation Axis (September 2026)

A generated app almost always issues a credential at sign-in and almost never decides when it stops working. We pre-register a seven-signal rubric for session lifetime and revocation, and name the trap that hides it.

Minimalist technical blueprint on deep navy: a thin-line key joined to a half-closed valve symbol, concentric arcs radiating from the valve, and a tick-marked measurement axis beneath, on a faint dotted grid. An abstract schematic of a credential, elapsed time, and a shut-off point.
Minimalist technical blueprint on deep navy: a thin-line key joined to a half-closed valve symbol, concentric arcs radiating from the valve, and a tick-marked measurement axis beneath, on a faint dotted grid. An abstract schematic of a credential, elapsed time, and a shut-off point.
On this page

Quick answer. A generated application almost always hands out a credential at sign-in and almost never decides when that credential stops working. The JSON Web Token specification makes the expiry claim optional. The cookie specification has no concept of server-side expiry at all, only a hint to the browser. And the dominant managed auth provider documents its default session as lasting indefinitely, with the controls that would bound it available only on a paid plan. None of that is visible while you build, because every session you observe during a build is a few minutes old and belongs to you. This is a proposal for a new BuilderProof axis measuring one narrow, checkable thing: once a generated application has issued a session credential, what ends it, how soon, and can anyone end it early. We are pre-registering the rubric before running it, so the method can be argued with before any number exists.

Why this needs its own axis

Sign-in is the part of a generated application everyone checks. You prompt for authentication, the builder scaffolds a login form, you create an account, you sign in, the dashboard renders. It works. You move on.

What you did not check, because it is not observable in that window, is what happens to the credential you just received. Not whether it was issued correctly, but whether anything will ever take it away.

Call it the fresh-session illusion. Every session anyone sees during development is minutes old, created by the developer, in the developer's own browser, on the developer's own machine, and abandoned rather than ended. Expiry is a property that only becomes visible after a delay the build never contains. Revocation is a property that only becomes visible from a device the build never has. Idle timeout requires you to stop working and wait. Absolute lifetime requires you to keep working and not stop. An application that ends sessions properly and an application that issues a permanent bearer credential produce byte-for-byte identical traces in every observation collected while it is being built.

This is the third axis in this series whose trap has that shape. The honest-caller illusion and the one-keyboard illusion share it: in each case every cheap piece of evidence available at build time is generated by a single actor, so the correct implementation and the broken one agree on all of it. Our text-comparison and collation axis describes the same structure on a different surface, where the single actor is one keyboard rather than one browser session.

What the sources actually say

IETF

Expiry is optional in the token format. RFC 7519 defines the expiration claim precisely: the exp claim "identifies the expiration time on or after which the JWT MUST NOT be accepted for processing." That is a strong requirement about how a token carrying the claim must be treated. The specification then adds one sentence that changes what a generated application is allowed to emit: "Use of this claim is OPTIONAL."

A token with no expiry claim is therefore not malformed. It is a conformant JWT that never expires, and a validator following the specification has nothing to complain about. The same optionality applies to nbf.

We recomputed the specification's own worked example rather than quoting it. RFC 7519 section 3.1 publishes both a base64url payload and, separately, the exact octet sequence that payload encodes. Decoding the base64url gives the claims set {"iss":"joe","exp":1300819380,"http://example.com/is_root":true}; rebuilding the payload from the published octet array produces byte-identical output, and re-encoding those octets reproduces the published base64url string exactly. The expiry value 1300819380 resolves to 22 March 2011, 18:43:00 UTC. The specification's example token does carry an expiry. The specification does not require that yours does.

The cookie attributes do not bind the server. RFC 6265 describes the Expires attribute as indicating "the maximum lifetime of the cookie" and immediately qualifies it: "The user agent is not required to retain the cookie until the specified date has passed." The Max-Age attribute carries the same qualification. Both are requests to a browser about how long to keep something, not statements to a server about how long to honour it.

Section 8.3 is more direct about the limit of what cookie handling can achieve. Even a server that encrypts and signs its cookies gets no protection here, because "encrypting and signing cookie contents does not prevent an attacker from transplanting a cookie from one user agent to another or from replaying the cookie at a later time." Replay at a later time is precisely the property this axis measures, and the cookie layer is documented as unable to constrain it.

OWASP

Both timeouts are required, and they do different jobs. The OWASP Session Management Cheat Sheet states that "All sessions should implement an idle or inactivity timeout" and, separately, that "All sessions should implement an absolute timeout, regardless of session activity." The reason they are two requirements rather than one is given plainly: the idle timeout "does not limit the attacker's actions, as they can generate activity on the session periodically to keep the session active for longer periods of time." An attacker in possession of a live session simply never lets it go idle. Only the absolute timeout "limits the amount of time an attacker can use a hijacked session."

The same document places enforcement unambiguously: "Session timeout management and expiration must be enforced server-side. If the client is used to enforce the session timeout, for example using the session token or other client parameters to track time references, an attacker could manipulate these to extend the session duration." And on sign-out: the application "must invalidate the session at least on server side."

Supabase

The unbounded session is the default, not a mistake. Supabase's user sessions documentation is unusually explicit about what arrives when nobody chooses: a session is created at sign-in and "By default, it lasts indefinitely and a user can have an unlimited number of active sessions on as many devices." The three mechanisms that would bound it, time-boxed sessions, an inactivity timeout, and a single-session-per-user rule, are introduced under a note reading "This feature is only available on Pro Plans and up."

So an unbounded session is not the result of a builder getting something wrong. It is the state you are left in by three independent defaults: a token format that makes expiry optional, a cookie layer with no server-side expiry concept, and a managed auth service whose sessions last forever until somebody upgrades a plan and changes a setting.

Two tensions the sources state themselves

The configured timeout is not the timeout. Supabase documents the behaviour of its own session limits candidly: "Sessions are not proactively destroyed when you change these settings, but rather the check is enforced whenever a session is refreshed next. This can confuse developers because the actual duration of a session is the configured timeout plus the JWT expiration time." A team that sets a fifteen minute inactivity timeout and a one hour token expiry has configured something whose real value is seventy five minutes. The documentation says so; the settings screen does not. This is why the rubric scores whether the stated lifetime matches the measured one, rather than scoring the stated lifetime.

Revocation detection cannot tell victim from attacker. RFC 9700, the OAuth 2.0 Security Best Current Practice, requires that authorization servers "MUST utilize one of these methods to detect refresh token replay by malicious actors for public clients," and describes rotation: the previous refresh token is invalidated while the relationship is retained, so "If a refresh token is compromised and subsequently used by both the attacker and the legitimate client, one of them will present an invalidated refresh token, which will inform the authorization server of the breach." Then it states the cost in the same paragraph: "The authorization server cannot determine which party submitted the invalid refresh token, but it will revoke the active refresh token. This stops the attack at the cost of forcing the legitimate client to obtain a fresh authorization grant."

The mechanism cannot distinguish a theft from a network retry. Its false positives are logged-out users.

The seam between the two documents

Neither source states the resolution on its own, and reading them against each other is where this proposal came from.

RFC 9700 explains why replay detection has an unavoidable cost, and offers no way to reduce it. Supabase documents what it does about that cost, and never says the cost is unavoidable in principle: a refresh token "can be used more than once within a defined reuse interval," by default ten seconds, plus a second exception where presenting the parent of the currently active token returns the active token instead of terminating the session. Its stated reason is the retry case, that clients "are inherently unreliable due to network issues" and a client that never received a response will retry with a token that was already spent.

Put together, the two documents describe something neither says alone: replay detection is not a correctness switch but a tuned trade, whose false-positive rate is a configuration decision about how much network unreliability to forgive. A builder that ships rotation without a reuse window has not been stricter, it has chosen a higher rate of spurious sign-outs and probably not on purpose. A builder that ships rotation with an unbounded window has switched the detection off while keeping its name.

Supabase is also candid about the limit of the whole mechanism, which is easy to skim past: it "does not guard against the case where a user's session is stolen from their device." Rotation protects against a leaked token, not against a compromised device. That distinction belongs in the rubric, and it is why rotation is not the heaviest signal here.

Proposed rubric

Seven signals, weighted, summing to 100. The failing case column is what a measurement run would actually record.

Scroll to see more

SignalWeightWhat a failing case looks like
Server-side termination on sign-out22Sign-out clears the credential in the browser and the server keeps accepting it, so a copy taken one minute earlier still works afterwards
Absolute session lifetime18No maximum lifetime exists, so a session created once is accepted indefinitely as long as it is used
Out-of-band revocation16There is no way to end a session on a device you no longer hold, short of waiting for an expiry that may not exist
Idle timeout14No inactivity expiry, or one enforced by a client-side timer that a caller can simply decline to run
Refresh-credential rotation and replay detection12A long-lived refresh credential that can be presented repeatedly, whose theft produces no signal at all
Privilege change re-binds the session10A role is revoked, a password is changed or a user is deleted, and sessions issued earlier keep the old authority until they expire
The stated lifetime is the real lifetime8The documentation names a timeout, and the measured refusal happens at a materially different time, or nothing is documented

Why the first signal carries the most

Signal 1 is heaviest for two reasons, and one of them is not about severity.

The first is that it is the only row whose failure contradicts something the user was explicitly told. Every other failure on this list is an absent protection. Sign-out is a promise: the interface presented a control, the user pressed it, and the application reported success. A session that survives it is not a missing feature, it is a false statement, and it is the specific one that matters on a shared or borrowed machine.

The second is that it is cheap and unambiguous to measure. It needs no waiting, no second device and no threshold anyone has to argue about: capture a credential, sign out, replay the credential. It either works or it does not. That makes a failure here unusually informative about the generator, because it is the one row where no design trade-off can explain the result away.

We note the counter-argument rather than hiding it. Out-of-band revocation, signal 3, covers the worse scenario, the device you no longer hold. We weight it below sign-out because it is a strictly larger capability that a competent stateless design may deliberately not offer, whereas honouring sign-out is a floor.

Four postures

Four structural states, in order. This is a description of design shapes, not a ranking of products, and no builder is placed in any of them here.

  1. Perpetual. A credential is issued and nothing ends it but the browser forgetting it. There is no expiry claim, no server-side record, and no control anywhere that would refuse it.
  2. Timed. The credential carries an expiry and stops working eventually. Nothing can stop it sooner. Sign-out is a client-side gesture; the window of exposure after a leak is fixed at issue time and cannot be shortened.
  3. Terminable. The server holds session state it can invalidate, so sign-out and administrative revocation take effect. Lifetime may still be unbounded while the session stays in use.
  4. Bounded. Terminable, plus an absolute limit and an idle limit enforced server-side, rotation with a tuned replay window, re-binding on privilege change, and a documented lifetime that matches the measured one.

The distinction between 2 and 3 is the one worth watching, because it is the one that cannot be added later without changing where session state lives.

Reproduction protocol

Ten steps. Each is either an observation or a control, and the run records the wall-clock time of every refusal.

  1. Generate an application that requires sign-in, from a prompt that does not mention sessions, expiry, timeouts or logout. Deploy it to the target the builder recommends, because these behaviours are properties of the deployment rather than of the repository.
  2. Sign in. Capture the complete credential set: every Set-Cookie response and every token placed in browser storage. Record the expiry attributes as sent, and record their absence explicitly rather than by omission.
  3. Attributability baseline. Copy the credential out of the browser and replay it from a clean client carrying no other state. If it is accepted, every later step is measuring the credential rather than the browser. If it is refused, record what else the server required and re-establish the baseline before continuing. Nothing after this step is interpretable without it.
  4. Sign out in the browser. Immediately replay the copy from step 3. A credential still accepted after a successful sign-out fails signal 1, and the run records how long it remains accepted.
  5. Leave a second copy completely idle. Replay it at intervals until it is refused. Record the elapsed time at first refusal, or record that no refusal occurred within the run window.
  6. Keep a third copy continuously active, issuing one request at an interval shorter than any idle timeout observed in step 5, and continue past any documented maximum lifetime. A session kept alive by activity alone, indefinitely, fails signal 2.
  7. From a second client, sign in as the same user. Then attempt to end the first session using every control the application offers: an administrative surface, a password change, and any sign-out-everywhere option. Replay the step 3 copy after each attempt and record which, if any, took effect.
  8. Change the user's role, and separately delete the user, while the step 3 copy is still live. Replay it. Record what authority it still carries and for how long after the change.
  9. If a refresh credential exists, present it twice and record whether the second attempt succeeds, whether it terminates the session, and whether any signal is raised to anyone. Then present the parent credential after a successful rotation and record the same three outcomes. This separates rotation from replay detection, which are commonly conflated.
  10. Control. Compare the refusal times measured in steps 5 and 6 against the numbers the builder's own documentation states. This step scores the gap, not the durations, so a builder that documents nothing cannot pass it by having convenient defaults, and a builder with long but accurately documented lifetimes is not penalised twice.

What this axis is not

Not our auth and access-control posture axis. That axis has a sub-axis, session and credential hygiene, scoring documented session management, password reset and rate limiting on authentication endpoints. It asks whether the machinery exists and is documented. This axis asks what an existing, correctly scaffolded session does over time. The two move in opposite directions in both directions: an application can score full marks for documented session management, password reset and auth rate limiting, and still issue a credential the server honours a year later with no way to end it early, because the mechanism exists, is documented, and simply never stops. And an application can document almost nothing about its session handling, scoring badly there, while its sessions are short-lived server-side rows that die on sign-out and can be deleted from an admin screen. That proposal names the gap itself in its own limitations section, observing that documentation is not a test and that a builder can document a primitive and still scaffold an app that forgets to enable it. Read the auth and access-control posture proposal for what that axis does cover.

Not our security-headers axis. Its heaviest signal, at weight 22, is session cookie attributes: HttpOnly, Secure and an explicit SameSite. Those are instructions to the browser about how to store the credential and when to attach it. This axis asks whether the server still accepts it. The boundary is not our invention; it is in the governing documents. RFC 6265 says the user agent "is not required to retain the cookie" for the lifetime an attribute names, and its security considerations say that even signing and encrypting the cookie does not prevent replay at a later time. OWASP says expiration "must be enforced server-side." Stated both ways: a session cookie can carry every attribute correctly, scoring full marks on that axis, and hold a value the server honours forever, so one successful copy is permanent. And a cookie can be set with no attributes at all, scoring zero there, while the value inside it is a ten minute server-side row an administrator can delete instantly. See what security headers does your generated app send for that axis.

Not a penetration test. Every step above runs against an application we generated, with credentials we were issued, using only documented interfaces. We do not attempt to forge, guess or crack anything, and a low score is a statement about defaults rather than a vulnerability report.

Not a verdict on any product. No builder is scored here. This is a rubric, a protocol and a set of postures, published before measurement so the method can be criticised while criticism is still free.

Open questions

We would rather publish these unresolved than pick answers quietly.

  1. What absolute lifetime is correct? OWASP requires that one exist and names no number. Supabase discourages access token lifetimes above one hour, and separately warns against values below five minutes because clock skew across user devices produces errors that are difficult to debug. The rubric currently scores presence, server-side enforcement and documentation rather than a threshold. A defensible threshold would be better than a convenient one, and we do not have one yet.
  2. Should a paywalled control count against the generated application? If the mechanisms that bound a session are available only on a paid plan, and the builder deploys to the free plan by default, the app as shipped cannot bound its sessions. Scoring the artifact as deployed counts that against it. Scoring the platform's capability would not. We currently do the former and are not certain it is right.
  3. Can stateless and stateful designs share one scale? A stateless token design can be excellent at rotation and, by construction, poor at revocation. A server-side session store is the reverse. Weights tuned on one may systematically mis-rank the other, and splitting the axis by design shape would trade comparability for fairness.

As with every proposal on this site, the rubric above is editable before it is applied. If a weight is wrong, or a signal is missing, or step 3 does not do what we think it does, that is the contribution we want.

References

  • IETF, RFC 7519, JSON Web Token (JWT), Standards Track, May 2015. Section 4.1.4, the exp claim and its optionality; section 3.1, the example JWT and its published octet sequence. Read 10 September 2026. https://www.rfc-editor.org/rfc/rfc7519.txt
  • IETF, RFC 6265, HTTP State Management Mechanism, Standards Track, April 2011. Sections 4.1.2.1 and 4.1.2.2, the Expires and Max-Age attributes; section 8.3, clear text and replay; section 8.4, session identifiers and session fixation. Read 10 September 2026. https://www.rfc-editor.org/rfc/rfc6265.txt
  • IETF, RFC 9700, Best Current Practice for OAuth 2.0 Security. Section 4.14, refresh token protection, rotation, and the stated cost of replay detection. Read 10 September 2026. https://www.rfc-editor.org/rfc/rfc9700.txt
  • OWASP, Session Management Cheat Sheet. Idle timeout, absolute timeout, renewal timeout, server-side enforcement, and manual session expiration. Read 10 September 2026. https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
  • Supabase, User sessions, Supabase Auth documentation. Default indefinite sessions, plan-gated session limits, the configured-timeout-plus-token-expiry note, refresh token reuse detection and the reuse interval. Read 10 September 2026. https://supabase.com/docs/guides/auth/sessions

Cite this benchmark

Plain text
BuilderProof editorial team. "Can You Actually Log Someone Out? A Session Lifetime and Revocation Axis (September 2026)". BuilderProof, September 2026. https://www.builderproof.org/benchmarks/can-you-actually-log-someone-out-session-lifetime-axis-september-2026.
BibTeX
@misc{builderproof-can-you-actually-log-someone-out-session-lifetime-axis-september-2026,
  title  = {{Can You Actually Log Someone Out? A Session Lifetime and Revocation Axis (September 2026)}},
  author = {{BuilderProof editorial team}},
  year   = {2026},
  month  = {sep},
  howpublished = {\url{https://www.builderproof.org/benchmarks/can-you-actually-log-someone-out-session-lifetime-axis-september-2026}},
  note   = {BuilderProof, builderproof.org}
}

Frequently asked questions

Why does a missing session lifetime stay invisible during development?

Because every session anyone observes while building is a few minutes old, created by the developer, in the developer's own browser, and abandoned rather than ended. Expiry only becomes visible after a delay the build never contains, and revocation only becomes visible from a device the build never has. An app that ends sessions properly and an app that issues a permanent credential produce identical traces in every observation collected at build time. We call this the fresh-session illusion.

Is a JSON Web Token without an expiry claim invalid?

No. RFC 7519 states that the exp claim identifies the time on or after which the token must not be accepted, and then states that use of the claim is OPTIONAL. A token carrying no expiry is a conformant JWT that never expires, and a validator following the specification has nothing to object to.

Do HttpOnly, Secure and SameSite limit how long a session lasts?

No, and that is the boundary between this axis and our security-headers axis. Those attributes tell the browser how to store a credential and when to attach it. RFC 6265 notes that a user agent is not required to retain a cookie for the lifetime an attribute names, and its security considerations state that even signing and encrypting cookie contents does not prevent replay at a later time. OWASP states that session expiration must be enforced server-side.

Why is refresh token rotation not the heaviest signal?

Two reasons drawn from the sources. RFC 9700 states that when rotation detects a replay the authorization server cannot determine which party submitted the invalid token, so it stops the attack at the cost of forcing the legitimate client to re-authenticate, which means its false positives are logged-out users. And Supabase notes that reuse detection does not guard against the case where a session is stolen from the user's device. Rotation protects against a leaked token, not a compromised device.