Cookies and Web Storage

A web application can keep data in your browser in three common places. Cookies are small name–value pairs that the server sets with a Set-Cookie response header and the browser sends back in a Cookie request header. Local storage and session storage, together called web storage, are key–value stores that only the page's JavaScript reads and writes; nothing in them reaches the server unless a script sends it.

A shop, for example, can use all three: a cookie keeps you logged in, local storage keeps your cart after you close the browser, and session storage keeps the checkout step of one tab. You met them in the Application panel in the lesson on browser DevTools; here you learn the rules that decide what survives a reload, a new tab, a restart and a logout, so you can predict the result before you test it.

Why it matters

Defects in this area are about the wrong data surviving or the right data disappearing: a user who is still logged in after logout, a cart that empties on refresh, a checkout that breaks in a second tab, or the previous user's address on a shared computer. They rarely appear in a happy-path run, because nobody closes the tab, restarts the browser or switches account.

Browser storage is also a security and privacy surface. A session cookie without HttpOnly can be read by any script on the page, a token in local storage stays on the device after the user leaves, and personal data kept in the browser can outlive its purpose. Knowing how each mechanism behaves turns these risks into planned tests.

Cookies from the browser's point of view

RFC 6265 defines how cookies work. The browser stores each cookie it accepts and attaches it to later requests whose host and path match, until the cookie expires. The attributes decide where a cookie goes and how long it lives, so they are what you test.

In the sample below, sid has no expiry, so it is a session cookie, and lang is persistent for one year (31,536,000 seconds). On later requests the browser sends only the name=value pairs, never the attributes, which is why you inspect attributes in the response or in the Application panel.

  • Expires (a date) or Max-Age (seconds) makes a cookie persistent; if both are set, Max-Age wins. Without either it is a session cookie, removed when the browser session ends.
  • Domain and Path choose which hosts and URLs receive it. Without Domain, only the exact host that set it does.
  • Secure sends it over HTTPS only; HttpOnly hides it from JavaScript, so document.cookie does not list it.
  • SameSite (Strict, Lax or None) is newer than RFC 6265 but supported by current browsers; it controls whether the cookie is sent with requests that start on other sites. None requires Secure; check that the application sets the value explicitly instead of relying on a browser default.
  • RFC 6265 asks browsers to support at least 4096 bytes per cookie and 50 cookies per domain. Every matching request carries the cookies, so large cookies add bytes to every request, and a server may reject a request whose headers grow too large.
HTTP/1.1 200 OK
Set-Cookie: sid=Yx81Tq0bA2; Path=/; Secure; HttpOnly; SameSite=Lax
Set-Cookie: lang=ar; Max-Age=31536000; Path=/; Secure; SameSite=Lax

GET /account HTTP/1.1
Host: shop.example
Cookie: sid=Yx81Tq0bA2; lang=ar

Try it: Cookie Analyzer Try it: HTTP Headers Parser

How the browser keeps a session

HTTP is stateless: each request stands alone. To keep you logged in, the server creates a session at login, keeps its data on the server and gives the browser only a random session identifier, usually in a cookie such as sid. The browser sends it with every request and the server looks the session up. The browser holds the key, not the session.

Some single-page applications instead receive a token, often a JWT (JSON Web Token: a signed string that carries information such as the user's identity and an expiry time), keep it in web storage or in memory and add it to an Authorization header themselves. Any script on the page can read web storage, which is why OWASP advises against keeping session identifiers or tokens there; an HttpOnly cookie is out of reach of scripts. Either way, the same session checks apply:

  • Login: the session identifier changes at login, so a value planted before login is useless afterwards.
  • Logout: the server invalidates the session. Deleting the cookie is not enough; a request replayed with the old identifier must be refused.
  • Timeouts: after the agreed idle and absolute time the session ends, and the user lands on the login page with a clear message, not an error.
  • Tabs: a session cookie is shared by every tab, so after logout in one tab the others must show the login page on their next action.

Try it: JWT Decoder

Local storage and session storage

Web storage is defined in the HTML Standard. Both stores keep string keys and string values, are separated by origin (scheme, host and port, so the http and https versions of a site do not share data) and are used only from JavaScript, for example localStorage.setItem('cart', value). Nothing is sent to the server automatically. They differ in lifetime and sharing:

  • Local storage is shared by every tab of the origin and has no expiry: it survives browser restarts until a script, the user or the browser clears it.
  • Session storage belongs to one tab. It survives reloads and is cleared when the tab closes; a new tab gets its own store, while duplicating a tab copies it.
  • Values are strings only. An object saved without first turning it into text with JSON.stringify becomes [object Object], a defect you can spot in the Application panel.
  • Space per origin is limited, typically a few megabytes depending on the browser. When it is full, setItem throws an error the application must handle.
  • A private window gets separate storage, which the browser discards when the private session ends.
A cookie goes to the server with every matching request and can be hidden from scripts with HttpOnly; local storage stays in the browser across tabs and restarts, session storage in one tab until it closes.

CookieRFC 6265

Set by the server with Set-Cookie, or by a script unless it is HttpOnly

Sent to the server:
Automatically, with every matching request
Shared between:
All tabs of the browser
Lifetime:
Until Expires or Max-Age; without them, until the browser session ends
Readable by page scripts:
Not when HttpOnly
First check:
After logout, the server refuses the old session value

Local storagelocalStorage

String keys and values written by the page's JavaScript

Sent to the server:
Never automatically
Shared between:
All tabs of the same origin
Lifetime:
No expiry: until a script, the user or the browser clears it
Readable by page scripts:
Always
First check:
User B never sees what user A left behind

Session storagesessionStorage

The same interface, scoped to one tab

Sent to the server:
Never automatically
Shared between:
One tab only; duplicating the tab copies it
Lifetime:
Until the tab closes; survives reloads
Readable by page scripts:
Always
First check:
A new tab starts without the old tab's step

Lifetimes shown are the defaults: a browser that restores the previous session on start-up also restores session cookies and session storage.

Worked example: login, cart and checkout

A shop sets sid as a session cookie, sets remember as a 30-day cookie only when the user ticks "Remember me", keeps the cart in local storage and the checkout step in session storage. Before testing, the tester predicts each result from the rules above, then checks it in the page and in the Application panel. The table assumes the user ticked "Remember me"; each cell is an expected result, and each row becomes a test case.

Reopening the browser without "Remember me" means logging in again with the cart still there; with it, the shop issues a new sid through remember, which must stop working after 30 days. Whether the cart should survive logout is a product decision, not the tester's: raise it as a question and record the answer in the test case.

Action                         sid       remember   cart (local)   step (session)
Reload the page                kept      kept       kept           kept
Open the shop in a new tab     sent      sent       same cart      empty
Close and reopen the browser   gone (1)  kept       kept           gone (1)
Log out                        deleted   deleted    decide (2)     gone
Clear site data                gone      gone       gone           gone

Old sid replayed after logout  -> login page or 401, never account data
Shop after clearing site data  -> loads logged out, empty cart, no error
(1) unless the browser restores the previous session on start-up
(2) product decision: on a shared computer the next user sees the cart

Testing persistence, clearing and privacy

Use a test account, and edit or clear stored data only on systems you are allowed to test. Cover five areas; the Console lines after the list show how to read and change web storage directly.

  • Persistence: reload, new tab, duplicated tab, browser restart and the moment of expiry, each compared with the requirement.
  • Clearing: delete one cookie or key, then clear all site data. The application must start cleanly, without a blank page or a script error.
  • Old and corrupted values: an invalid or old-format value, as after a release that changed the format, must not break returning users.
  • Shared computer: log in as user A, log out, log in as user B. Nothing of A — name, address, cart, searches, drafts — may appear for B.
  • Privacy: passwords and card numbers never belong in cookies or web storage, other personal data only while a feature needs it, and session cookies carry Secure and HttpOnly. If the site asks for cookie consent, refuse non-essential cookies and confirm in the Application panel that none were set.
localStorage.getItem('cart')          // read what the shop stored
localStorage.setItem('cart', '{bad')  // invalid JSON: reload, does the page recover?
sessionStorage.clear()                // empty this tab's session storage
document.cookie                       // HttpOnly cookies are not listed

Common mistakes

  • Testing in one long-lived tab only, so new-tab, restart and logout behaviour is never observed.
  • Passing logout because the cookie disappeared, without checking that the server refuses the old session identifier.
  • Assuming session storage behaves like a session cookie: the cookie is shared by all tabs and goes to the server, session storage belongs to one tab and stays in the browser.
  • Reporting a session cookie that survived a restart without checking whether the browser restores the previous session on start-up.
  • Testing only with a clean browser, so returning users with old cookies or an old storage format are never simulated.
  • Pasting cookie values, tokens or storage contents with personal data into a bug report instead of masking them.

The QA perspective

Ask early what the feature keeps in the browser, where and for how long. A short table — name, mechanism, attributes, lifetime, what logout does — is a test basis that developers, the product owner and security reviewers can all check. Open points such as "Should the cart survive logout?" or "How long does Remember me last?" are requirement questions, not tester decisions.

In a bug report, name the exact cookie or key, its attributes or value as the Application panel shows them with secrets masked, the browser and its version, and the step that changed it: login, new tab, restart or logout. Storage behaviour often regresses after framework upgrades and after releases that change stored formats, so keep these checks in the regression suite and run them on every supported browser.

Practice exercise

Predict and check what a staff portal keeps

Your task

After login, a staff portal sets the cookies and storage below. For each item, say how long it lives and whether it is shared between tabs. Then list the defects or risks you would report, and three test cases that confirm logout works.

Material
Response to POST /login
Set-Cookie: session=Qm91c2E1Vq; Path=/
Set-Cookie: theme=dark; Max-Age=86400; Path=/; SameSite=Lax

localStorage on https://portal.example
  user    {"name":"Sara Ali","email":"sara@example.com","token":"eyJhbGciOi..."}
  draft   "Notes for the Sunday meeting"

sessionStorage on https://portal.example
  wizard  {"step":3}
Steps
  1. Classify each item: cookie, local storage or session storage; persistent or session.
  2. Predict what remains after a new tab, a browser restart and a logout.
  3. Check each cookie's attributes and each storage key's contents for security and privacy problems.
  4. Write the logout test cases with their expected results.
Show the model answer
Model answer

session has no Expires or Max-Age, so it is a session cookie shared by all tabs and removed when the browser session ends. theme is persistent: it lives 86,400 seconds (one day), is shared by all tabs and survives a restart within that day. user and draft stay in local storage across tabs and restarts until cleared. wizard exists only in the tab that created it: a new tab starts without it, and it disappears when that tab closes.

Defects and risks: session lacks Secure and HttpOnly, so it can travel over plain HTTP and any script can read it; it also has no explicit SameSite. The user key keeps a token any script can read, and the user's personal data stays on the device after the browser closes. Pasting the two Set-Cookie lines into the Cookie Analyzer lists the missing attributes.

Question for the product owner: should draft survive logout? On a shared computer the next user would see Sara's notes.

Logout tests: (1) after logout, session is deleted and user is removed from local storage; (2) a request replayed with the old session value, or with the old token, returns the login page or 401 Unauthorized, not portal data; (3) after logout in one tab, the other tab shows the login page on its next action.

Frequently asked questions

Is a session cookie the same as session storage?

No. A session cookie is a cookie without Expires or Max-Age: the browser sends it to the server, shares it across all tabs and removes it when the browser session ends. Session storage is a JavaScript store that belongs to one tab, is never sent to the server automatically and is cleared when that tab closes.

Why is my session cookie still there after I restarted the browser?

Many browsers can restore the previous session on start-up, and they then restore session cookies too, as if the browser had never closed. Check the browser's start-up setting, test the requirement with session restore turned off, and record the setting in the bug report.

Where should an application keep the login token?

That is a design decision for developers and security reviewers, but a tester should know the trade-off: an HttpOnly, Secure cookie cannot be read by scripts, while anything in web storage can. A cookie, on the other hand, is sent automatically, so it needs protection against cross-site request forgery (CSRF): anti-CSRF tokens, with SameSite as an extra layer. Whatever the choice, test that the credential is cleared at logout and refused by the server afterwards.

References

Practise with Mutqan tools

Parse Set-Cookie headers and check Secure, HttpOnly, SameSite, Domain, Path, expiry and size best practices.

Validation

Parse raw HTTP request/response headers into a structured table with explanations.

Web

Decode JWT header and payload, inspect claims and check expiration — entirely in your browser.

Security

Go deeper

Your progress on this topic

Part of the QA Roadmap