Session Management Testing
Session management testing checks how an application creates, protects, renews and ends the sessions that keep users signed in. It asks five questions: can identifiers be predicted, can an identifier planted before login be used after it (session fixation), are the idle and absolute timeouts enforced, does logout end the session on the server, and does every session cookie carry the right flags?
Example: an HR portal keeps the same identifier from the login page to the dashboard. Login works, so every functional test passes, yet anyone who placed that value in an employee's browser before login now shares the employee's session. Only a test that replays the old identifier finds it.
Why it matters
To the server, whoever sends a valid session identifier is the signed-in user, with no password and no second factor. A predictable, planted, leaked or never-ending identifier therefore turns a strong login into a weak one.
These defects never show in functional testing, because the user's own journey works. Only tests designed for them find them, and most are cheap: two browser profiles, developer tools and a test account. The OWASP Web Security Testing Guide (WSTG) has a whole category of session management tests; this lesson turns its core ones into test conditions you can run on every release.
The session lifecycle as a state model
In the lesson on cookies and sessions over HTTP you traced one session by hand. A security test plan must cover every way a session starts, changes and ends, and a state model, as in state transition testing, makes that visible: no session, an anonymous session (many applications create one before login), signed in, idle, and ended. Each transition is a test condition.
One rule covers the whole model: at login, at every privilege change and at every way a session ends, the old identifier must stop working on the server; only the moves between signed in and idle keep the same identifier. The check is always the same: keep a copy of the old identifier, replay it after the transition, and expect the login page or 401, never the user's data. The expected values, such as both timeouts and the events that end other sessions, come from the team's session policy.
| # | From | To | Event / what the server must do |
|---|---|---|---|
| 1 | No sessionStart | Anonymous session A | First visit / the server issues pre-login id A (if the application creates one) |
| 2 | Anonymous session A | Signed in BCurrent state | Login succeeds / new id B; replaying A now gets the login page |
| 3 | Signed in BCurrent state | Signed in BCurrent state | Privilege change (re-authentication, role switch) / new id; the previous id stops working |
| 4 | Signed in BCurrent state | Signed in, idle | No request from the user |
| 5 | Signed in, idle | Signed in BCurrent state | A user request before the idle limit / the idle timer restarts |
| 6 | Signed in, idle | Ended on the serverEnd | Idle limit reached on the server's clockalternative path |
| 7 | Signed in BCurrent state | Ended on the serverEnd | Logout / the server deletes the session record |
| 8 | Signed in BCurrent state | Ended on the serverEnd | Absolute limit reached, password change or revocation by an administratoralternative path |
A simplified model: real applications may add states such as a remembered device or a locked account.
Identifier randomness: look for structure
The OWASP Session Management Cheat Sheet asks for identifiers from a cryptographically secure random generator with at least 64 bits of entropy; version 5.0 of the OWASP Application Security Verification Standard (ASVS) asks for at least 128 bits, so use the minimum your team's policy adopts. You cannot prove randomness by looking, but you can find the structure that disproves it. Log in about 20 times with one test account and a few times with another, record each identifier with its time, and line them up.
- Length gives only an upper limit: 32 hexadecimal characters carry at most 128 bits. If 24 of them never change, the other 8 carry at most 32 bits, below the minimum.
- A prefix that every value shares, or a part that grows by one at each login, is a counter, not randomness.
- Decode the value as Base64 and convert long numbers as Unix timestamps (seconds or milliseconds since 1 January 1970 UTC): a user id, an email or the login time inside means the identifier is built, not random.
- A second login by the same user must give a completely different value, and the identifier must never appear in URLs, page source or error messages.
- These checks find structure but cannot prove its absence. Statistical analysis of a large sample, for example with the Sequencer in Burp Suite, is specialist work; the best evidence is that the framework's built-in session manager creates the identifier, which OWASP recommends over a home-made one.
Worked example: testing session fixation
In session fixation the attacker plants an identifier instead of stealing one: they obtain a valid pre-login identifier, get it into the victim's browser, for example through a link that carries it in the URL or a cookie set from a sibling subdomain, and wait for the login. If the application keeps that identifier, the attacker's copy is now signed in.
Set-up: a staff portal, https://hr.example, in a test environment, one test account and two browser profiles, A for the attacker and B for the employee. Test only systems you own or have written permission to test. The trace shows A's visit, B's login after the cookie was set by hand, and A's replay.
- In A, open the login page and copy the pre-login
sid. In B, setsidto that value in the storage panel of the developer tools, then log in. - Expected: the login response sets a new
sid, and reloading/dashboardin A leads to the login page. If A sees the employee's dashboard, the session is fixed: propose critical severity. - Repeat with a value the server never issued, such as
sid=tester-made-0001. A strict session manager ignores it and issues its own; OWASP calls one that accepts it permissive. Report that even if renewal still protects the login. - Repeat at every privilege change: re-authentication before a sensitive action, a switch of role or organisation, and another user logging in on the same browser. When the session travels in a cookie, an identifier in the URL, such as
?sid=or;jsessionid=, must be ignored.
GET /login HTTP/1.1
Host: hr.example
HTTP/1.1 200 OK
Set-Cookie: sid=Pq7Lm2Vx9Rt4Kw8N; Path=/; Secure; HttpOnly; SameSite=Lax
POST /login HTTP/1.1
Host: hr.example
Cookie: sid=Pq7Lm2Vx9Rt4Kw8N
HTTP/1.1 303 See Other
Location: /dashboard
Set-Cookie: sid=Zc4Hn8Tq1Wy6Bd3F; Path=/; Secure; HttpOnly; SameSite=Lax
GET /dashboard HTTP/1.1
Host: hr.example
Cookie: sid=Pq7Lm2Vx9Rt4Kw8N
HTTP/1.1 302 Found
Location: /loginTimeouts and logout: every way a session must end
You already test both timeouts with boundary values and replay the identifier after logout. At security level, also test what keeps a session alive by accident and every event that should end one. Note the time of each request: the server's clock decides, not the page.
- Background traffic: leave a page that polls the server, such as a notification counter, idle past the limit. If polling counts as activity, the session never idles out; ask whether that is intended.
- Warning dialogs: "Stay signed in" must extend the session on the server, and ignoring the warning must end it there, not only hide the page. No activity extends the absolute timeout.
- Tabs and devices: logout in one tab ends the session in every tab, and "Log out of all devices" also ends the session in a second browser profile.
- Password change: the ASVS expects an option to end all other sessions after a change of any authentication factor, such as a password change, including one made through password reset, or an update of the MFA settings. It also expects every session of a disabled or deleted account to end; ask what the policy says about removed roles.
- Every credential: a remember-me cookie (a persistent cookie that signs the user in again without the password), a refresh token (a long-lived token that obtains new access tokens) or the access token of a single-page app must also stop working at logout. Replay each one.
- After logout the Back button may redisplay a page from the browser's history. That is a caching question; reload or click anything, and the server must answer with the login page.
Common mistakes
- Judging randomness from two or three identifiers that merely look different.
- Testing renewal only with the identifier the server issued, never with one planted from another profile or invented by the tester.
- Ending a timeout test when the page shows a warning or a login form, without checking what the server answers to the next request.
- Replaying only the main session cookie after logout and forgetting remember-me cookies, refresh tokens and API tokens.
- Checking cookie flags on the login response only, not on renewals, refreshes and "remember me" logins.
- Testing a system without written permission, or pasting live identifiers into reports and chats instead of masking them.
The QA perspective
Plan session tests from the first feature behind a login, with the session policy as test basis. Ask the product owner and the security lead for the values and the rules, and turn each transition of the state model into a test case whose expected result includes the replay of the old identifier.
Most of this behaviour is framework or gateway configuration, so rerun the tests after upgrades and infrastructure changes. In a bug report, give the transition, the masked identifiers before and after, timestamps and the server's answer. Leave exploitation and statistical analysis to security specialists, and agree the scope with them before testing anything outside the test environment.
Practice exercise
Judge the first session tests of a pharmacy portal
A pharmacy portal in your test environment, which you have written permission to test, has this session policy: idle timeout 15 minutes and absolute timeout 8 hours, both on the server; a new identifier at login and at every privilege change; logout ends the session; a password change ends all other sessions.
Your first tests produced the material below and three observations: the pre-login sid, replayed after login, opens the dashboard; a tab that polls /api/notifications every 60 seconds is still signed in after 40 idle minutes; after a password change in profile 1, profile 2 still loads the order history. For each finding, name the area, the rule it breaks, a proposed severity and a confirmation test for the fix.
Identifiers after five logins of test user 2041
sid=s-2041-8f3a01
sid=s-2041-8f3a02
sid=s-2041-8f3a03
sid=s-2041-8f3a04
sid=s-2041-8f3a05
Login response without "remember me"
Set-Cookie: sid=s-2041-8f3a05; Path=/; Secure; HttpOnly; SameSite=Lax
Login response with "remember me"
Set-Cookie: sid=s-2041-8f3a06; Path=/; Max-Age=2592000- Line up the identifiers and mark the parts that never change and the parts that count.
- Compare the two
Set-Cookielines attribute by attribute. - Place each observation on a transition of the session state model.
Show the model answer
Randomness: 2041 is the user id, 8f3a0 never changes and the last digit counts up, continuing to 06 at the next login. Anyone who sees one identifier can guess the others. Rule: OWASP's random identifier with at least 64 bits of entropy and no meaning; here only one hexadecimal character, at most 4 bits, ever changes. Critical. Confirmation: a fresh sample shows no shared, growing or readable parts, and the team confirms that the framework's secure generator creates the identifier.
Fixation: the post-login identifiers contain the user id, so the identifier does change at login, but the pre-login one still opens the dashboard: renewal without invalidation leaves a planted value usable. Rule: a new identifier at login, with the old one ended on the server. Critical. Confirmation: the two-profile test from this lesson; A's replay after B's login reaches the login page.
Timeout: polling resets the idle timer, so the 15-minute rule never applies while the tab is open. Major, unless the policy says polling counts as activity. Confirmation: with the polling tab open and no user action, the first action after 16 minutes reaches the login page, and one after 14 minutes still works.
Revocation: the password change did not end the other session, so someone who stole a session keeps it after the user changes the password. Critical. Confirmation: after a change in profile 1, the next request in profile 2 reaches the login page; repeat with a change through password reset.
Cookie flags: the "remember me" response drops Secure, HttpOnly and SameSite and keeps the session cookie for 30 days. Rule: the session cookie baseline on every response that sets the cookie. Major; also ask whether "remember me" should use a separate, revocable cookie. Confirmation: the Cookie Analyzer shows the same flags on both responses.
Still to test: the 8-hour absolute timeout under steady activity, the replay of sid and of the remember-me cookie after logout, and renewal at privilege changes.
Frequently asked questions
Can I test session management without security tools?
Mostly, yes. Two browser profiles, developer tools, a test account and a command-line client such as curl cover renewal, fixation, timeouts, logout and cookie flags. Statistical randomness analysis and automated scanners are specialist work; involve the security team when the risk calls for it.
What changes when the application uses JWTs instead of a session identifier?
The questions stay; the checks change. The payload of a signed JWT is only encoded, not encrypted, so decode it and check that it holds no sensitive data and has a short exp. The server usually keeps no record of a JWT, so logout and password change need extra design, such as short-lived access tokens with revocable refresh tokens: replay both after logout to see what the application really does.
How many identifiers do I need to judge randomness?
For the visual checks in this lesson, a few dozen show counters, timestamps and fixed parts. No sample you read by eye proves that values are random enough; that needs statistical analysis or evidence of a proven generator. Report what you observed, not that the identifier is secure.
Is a missing renewal a defect if nobody can plant an identifier?
Yes. Missing renewal is the weakness that fixation exploits, and planting routes such as a cookie set from a sibling subdomain or an injected script can appear later. The OWASP Session Management Cheat Sheet makes a new identifier at login mandatory regardless.
References
- OWASP Web Security Testing Guide (WSTG)The reference methodology for testing web applications and APIs for security weaknesses, test by test.
- OWASP Cheat Sheet Series: Session ManagementSession identifier entropy, cookie attributes, expiry, renewal after login and logout handling.
- OWASP Application Security Verification Standard (ASVS)A checklist of verifiable security requirements for web applications, grouped in three assurance levels.
- Using HTTP cookies — MDNCookie lifetime, scope, the Secure/HttpOnly/SameSite attributes and their security implications.
Practise with Mutqan tools
Parse Set-Cookie headers and check Secure, HttpOnly, SameSite, Domain, Path, expiry and size best practices.
Define states and events, then generate a transition table, valid/invalid transition tests and 0-switch/1-switch coverage.
Decode Base64 or Base64url strings to text, with automatic padding repair.
Convert timestamps (seconds, milliseconds, microseconds) to human dates and back, in any timezone.
Build cURL commands from method, URL, headers, body and auth options.
Decode JWT header and payload, inspect claims and check expiration — entirely in your browser.
Go deeper
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.