How to test session timeout: idle logout, token reuse, and the back button
Session timeout isn't 'wait and get logged out.' It's whether the token is actually dead, whether a submit after expiry is refused, and whether the back button still shows private data. Here's how to test session timeout end to end in plain English.
How to test session timeout: idle logout, token reuse, and the back button
The session timeout demo is boring on purpose: log in, walk away, come back, get bounced to the login screen. Everyone builds that, most people test that, and it proves almost nothing. Because the interesting question isn't "did the UI show me a login page after 30 minutes" — it's "is the session I just abandoned actually dead, or is it merely hidden behind a screen a determined browser can walk right past?" An idle timeout that logs you out visually but leaves the token valid on the server is not a timeout. It's a screensaver.
Session timeout is a security control, and OWASP treats it as one. The Web Security Testing Guide is direct: testers should check "that the application automatically logs out a user when that user has been idle for a certain amount of time, ensuring that it is not possible to 'reuse' the same session and that no sensitive data remains stored in the browser cache." Two clauses do all the work there: not possible to reuse the same session, and no sensitive data remains. Neither is provable by watching a login form appear. This is the playbook for testing the parts that actually matter, in plain-English prompts you point at staging.
Why "you got logged out" is the wrong assertion
OWASP is explicit about where timeout has to be enforced: "Session timeout management and expiration must be enforced server-side. If some data under the control of the client is used to enforce the session timeout... an attacker could manipulate these to extend the session duration." That sentence is the whole reason a UI-only check is worthless. If your app decides "logged out" by reading a timestamp the browser holds, then the browser can lie about the timestamp, and the redirect to /login is theater — the session on the server never ended.
The correct assertion is behavioral: after the idle period, the old session must be unusable. Navigate to a protected page and you get refused, not served from cache. Submit a form and it's rejected, not processed against the dead session. Hit the back button and the private screen you left behind is gone, not preserved as a pixel-perfect snapshot of someone's account. OWASP names that last one specifically — the classic scenario is a public computer where, if the user walks away without logging out and no timeout is enforced, "an attacker could access to the same account by simply pressing the 'back' button of the browser." A timeout that survives the back button is the bar.
What "testing session timeout" actually has to cover
Getting bounced to login is one line. Here's the surface that isn't:
- The idle logout fires at all. The baseline OWASP method — "logging in and waiting for the timeout log out to be triggered." A protected app with no idle timeout is, in OWASP's words, "not secure, unless such behavior is required by a specific functional requirement."
- The session is dead, not hidden. After the timeout, requesting a protected page or API must be refused. "After the timeout has passed, all session tokens should be destroyed or be unusable." Serving the page from browser cache while the server session is already gone is a different bug than not timing out at all — and just as findable.
- A submit after expiry is refused cleanly. The nastiest real-world case: a user fills a long form, gets timed out mid-thought, and hits Submit. The app must refuse it and route them to re-authenticate — not process the write against a dead session, and not throw a stack trace because it assumed a live user.
- The back button and browser cache. After logout or timeout, pressing back must not resurrect the private screen. If sensitive data is still sitting in the page cache, the timeout failed clause two of the OWASP definition even if it nailed clause one.
- "Remember me" vs. idle timeout. A persistent-login option changes the rules, and it's where teams get sloppy — does "remember me" legitimately extend the idle window, or does it accidentally disable the timeout entirely? Both are decisions; only one is usually intended.
- Multiple tabs and concurrent sessions. Two tabs open, one goes idle and times out — what happens in the other? Log out in one tab; is the second tab still holding a live session a moment later? Concurrency is where "we invalidated the session" quietly becomes "we invalidated one reference to it."
A scripted suite almost never covers 2 through 6, and the reason is structural: the script was written to test the intended flow, and every one of these lives in the flow the user takes by accident — the abandoned form, the back button, the second tab. You have to think like someone misusing the app to write the assertion, which is the same reason an agent finds the bugs a script walks past.
One honest note on waiting
Idle timeouts are, by definition, slow — a real one might be 15, 30, or 60 minutes. OWASP notes a home-banking app should cap it around 15 minutes; a public forum can tolerate 60. For testing, don't wait out production durations by hand. Configure a short idle timeout in your staging Environment (say, two or three minutes) so the behavior is the same but the clock is fast, and test that. You're verifying the mechanism — does expiry invalidate the session server-side — not the specific number, which is a config value you can read off directly. Point the agent at the short-timeout staging build and the waits become practical. Substitute your own URLs and the real idle interval throughout.
Prompt 1: the idle logout, and whether the session is actually dead
Step 2 is the distinction a screenshot-only test can't make. "Redirected to login" and "showed the account balance for 400ms, then redirected" look nearly identical to a human and completely different to an attacker. The agent watches the network request for the protected page and reads what actually rendered, so a page served warm from cache after the server session died comes back as the leak it is, not as a passing redirect.
Prompt 2: the form submitted after the session expired
This is the case that generates the "I lost my work" support ticket and the security finding, at the same time. A write that succeeds against an expired session means the session wasn't really expired — clause one and clause two of the OWASP definition both failed. A write that 500s means the app never considered that a user might be gone by the time they click. The right answer is neither: refuse the write, keep the user's input where you can, and send them to log in. The agent tells you which of the three you actually shipped.
Prompt 3: the back button, remember-me, and a second tab
Step 4 is the concurrency trap. "We invalidated the session on logout" is easy to believe when you only ever test one tab, because the tab you clicked logout in behaves. The second tab is holding a reference the server was supposed to kill, and whether it's actually dead depends on server-side invalidation being real rather than a cookie deletion in one browser context. OWASP's gray-box guidance is exactly this: the log out must effectively destroy all session tokens, or at least render them unusable, and the server must perform "proper checks on the session state, disallowing an attacker to replay previously destroyed session identifiers." Two tabs is how you test that from the outside — the same cross-context reasoning the access-control playbook uses to prove one tenant can't reach another.
Reading the results
Each run comes back as a Monito Session: a screenshot timeline of every state — logged in, idle, post-timeout, back-button, each tab — plus the network log with the actual requests and their status codes, console output, and the agent's reasoning where it judged whether a page was genuinely refused or merely redirected. For session testing the network log is the evidence that matters most, because the entire question is server-side: each network event is captured with its method, URL, and status, so a protected request that returned 200 with real data after the timeout is provably a failure, and a clean 401/302 is provably a pass. The screenshot timeline is where the back-button and cached-content bugs show themselves.
None of the prompts names a selector or a DOM path — only the behavior being tested. Auth screens and session handling get rebuilt often, and a test that describes "stay idle, then confirm the session is dead" survives every one of those rebuilds. That's why describing intent beats scripting steps for anything as security-sensitive and redesign-prone as authentication, and it's the same durability the magic-link auth playbook and the password-reset playbook rely on.
The one to run right now
If you run a single check today, run the expired-session probe — it proves the one property that separates a real timeout from a screensaver: the abandoned session is actually dead server-side.
Save it as a Test Scenario, pin it to your staging Project, and wire it into CI against every deploy that touches auth, sessions, or middleware — the surfaces where a timeout quietly stops timing out. A full run is typically 8–13 credits — roughly $0.08–$0.13 — and your first run is free. One honest caveat: a browser agent tests session expiry from the outside, the way a real user (or attacker) would — which is exactly the coverage most suites lack — but it doesn't replace a proper security review of your session-management code. Treat it as the continuous, every-deploy proof that the timeout you configured still actually ends the session. Point it at a short-timeout staging build, walk away, and come back to find out whether "logged out" means dead or just hidden.