The Storage Panel

🗄️ Lesson 6 — The Storage Panel

The Storage panel shows everything the browser is holding on behalf of the current site — cookies, localStorage, sessionStorage, IndexedDB, and the Service Worker cache — with the ability to read, add, edit, and delete entries directly. Open it with Shift + F9.

🗺️ The Storage Panel Interface

Storage
🍪 Cookies
https://example.com
📦 Local Storage
https://example.com
🕐 Session Storage
https://example.com
🗃️ IndexedDB
myApp-db
▸ users
▸ sessions
⚡ Cache Storage
app-cache-v2
Cookies — https://example.com 🔄 🗑️ Delete All
Name Value Domain Path Expires HttpOnly Secure SameSite
session_id eyJhbGci…Xk3Q example.com / 2024-12-31 Strict
_ga GA1.2.1234567 .example.com / 2026-01-01 Lax
theme_pref dark example.com / Session Lax
tracking_id abc123 .example.com / 2025-06-01 None ⚠

The session_id row (highlighted) is a well-configured session cookie — HttpOnly ✓, Secure ✓, SameSite Strict. The tracking_id cookie has SameSite=None without the Secure flag — a misconfiguration that modern browsers will reject.

🍪 Cookies

Cookies are small key-value pairs the server sends via Set-Cookie headers. The browser automatically attaches them to every matching request — making them the standard mechanism for session authentication.

Security Flags

These three flags are the most important columns when auditing cookie security:

HttpOnly
JavaScript cannot read this cookie via document.cookie. The primary defence against XSS attacks stealing the session token — even injected scripts can't touch it.
Must be set on session cookies
Secure
Cookie is only sent over HTTPS. Without this, the cookie can be intercepted on any unencrypted connection (HTTP, public Wi-Fi).
Must be set on any sensitive cookie
SameSite
Controls whether the cookie is sent on cross-site requests. Prevents CSRF attacks where a malicious page triggers an authenticated request to your server.
Should be Lax or Strict

SameSite Values

Strict
Cookie only sent when navigating from the same site. Maximum security — but breaks links from external pages that land on authenticated views.
Lax
Sent on top-level navigations (clicking a link) but not on cross-site subresource requests. Browser default. Best balance of security and usability.
None
Sent on all cross-site requests. Requires Secure flag. Only for third-party cookies (payment iframes, embedded widgets). High CSRF risk if misused.

Editing Cookies in DevTools

💡 Test Auth Flows Without Code Edit a value: double-click any Value cell and type a new value — the browser uses it immediately on the next request.

Delete a cookie: select a row → press Delete, or right-click → Delete Cookie.

Add a cookie: click the empty row at the bottom of the table and type a name and value.

Try setting the session cookie value to INVALID and making an API call — the server should return 401. If it returns 200, the server isn't validating tokens.
ℹ️ Decoding a JWT Cookie Copy the value of any JWT cookie (the long eyJ… string). Paste it into jwt.io in a browser. The payload is decoded instantly — you can read the sub, exp, role claims without any code. Remember: a JWT is signed, not encrypted — the payload is readable by anyone who has it.

📦 Local Storage

A persistent key-value store tied to the current origin. Data survives browser restarts indefinitely — there is no expiry. Values are always strings; objects are stored as JSON.

Local Storage — https://example.com 4 items
theme
"dark"
language
"en-GB"
user_prefs
{"sidebar":true,"density":"compact","fontSize":14}
auth_token
eyJhbGciOiJIUzI1NiJ9… ⚠ insecure location
⚠ Don't store auth tokens in localStorage
localStorage is readable by any JavaScript on the same origin. An XSS attack that injects a script can steal everything in localStorage — including authentication tokens. The auth_token entry shown above is a common anti-pattern. Auth tokens belong in HttpOnly cookies, which JavaScript (including injected scripts) cannot read.

Interact with localStorage from the Console without touching the Storage panel:

// Read a value localStorage.getItem('theme') // → "dark" // Write a value localStorage.setItem('theme', 'light') // Delete one key localStorage.removeItem('auth_token') // List all keys and values for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); console.log(key, localStorage.getItem(key)); }

🕐 Session Storage

Identical API to localStorage, but scoped to a single browser tab and cleared when that tab closes. Two tabs open on the same page have completely separate sessionStorage.

BehaviourlocalStoragesessionStorage
Survives page reload
Survives browser restart
Survives tab close
Shared between tabs (same origin)✗ — per tab
Good for multi-step form stateRisky — persists after close✓ Ideal
💡 "I Lost My Data When I Opened a New Tab" If a user reports this, the data was almost certainly in sessionStorage. The fix is to migrate to localStorage (if the data should persist) or redesign the flow so it doesn't depend on tab continuity.

🗃️ IndexedDB

A full client-side database — asynchronous, transactional, and capable of storing structured JavaScript objects, Blobs, and large datasets. Used by PWAs for offline support and by apps that need complex queries over cached data.

IndexedDB — myApp-db → users (object store) 🔄 Refresh
#1
{ id: 1, name: "Alice", role: "admin", lastSeen: "2024-11-14" }
#2
{ id: 2, name: "Bob", role: "user", lastSeen: "2024-11-10" }
#3
{ id: 3, name: "Carol", role: "user", avatar: Blob(48321 bytes) }
ℹ️ Testing First-Run Experience Right-click any database name in the Storage tree → Delete Database. This wipes all object stores and records. Reload the page — it should behave exactly as it would for a brand-new user who has never visited. Essential for testing onboarding flows.

⚡ Cache Storage

Service Worker-controlled storage for complete HTTP request/response pairs. Unlike the browser's own HTTP cache, this is entirely under application control — a Service Worker script decides what to cache and what to return offline.

Cache Storage — app-cache-v2 7 entries
https://example.com/
200 OK · text/html · 12 KB · cached 2024-11-14
/style.css
200 OK · text/css · 8 KB
/app.js
200 OK · application/javascript · 142 KB
/api/config
200 OK · application/json · 1.2 KB
💡 Testing Offline Mode Set the Network panel throttle to Offline, then reload. If a Service Worker with a working cache strategy is installed, the page loads from Cache Storage. Open Cache Storage in the Storage panel and compare what's cached against what the page needs — any missing URL will fail offline.

📊 Storage Types at a Glance

Storage TypeControlled byStoresMax SizeSurvives Tab CloseJS Readable
Cookies Server + JS Strings (4 KB each) ~4 KB / cookie Until expires Only if not HttpOnly
localStorage JavaScript Strings ~5–10 MB ✓ (XSS risk)
sessionStorage JavaScript Strings ~5 MB ✓ (XSS risk)
IndexedDB JavaScript Structured objects, Blobs ~50% of disk
Cache Storage Service Worker HTTP request/response pairs ~50% of disk ✓ (SW only)

📏 Checking Storage Quota

Run this in the Console to see how much storage the current origin is using:

const est = await navigator.storage.estimate(); console.log(`Used: ${(est.usage / 1024 / 1024).toFixed(2)} MB`); console.log(`Quota: ${(est.quota / 1024 / 1024).toFixed(0)} MB`);

When storage fills up, writes to localStorage/sessionStorage throw a QuotaExceededError. IndexedDB and Cache Storage may be evicted by the browser under disk pressure unless the origin has called navigator.storage.persist().

✏️ Exercises

  • Audit cookie security. Open the Storage panel (Shift + F9) on a site you're logged into. Find the session cookie. Are HttpOnly, Secure, and SameSite all set? Note any cookies that are missing flags they should have.
  • Decode a JWT. Copy a JWT cookie value (the eyJ… string) and paste it into jwt.io. Read the decoded payload — what claims does it include? Note the exp timestamp and convert it to a human-readable date.
  • Test an expired session. In the Storage panel, edit the session cookie value to INVALID. Reload the page or trigger an API call. Does the server return 401? Restore the original value.
  • Explore localStorage. Open localStorage for any site. Are any values stored as JSON? Double-click a value to edit it — change a preference (theme, language, etc.) and reload. Does the page reflect the change?
  • Verify sessionStorage isolation. Open two tabs on the same page. In the first tab's Storage panel, add a key to sessionStorage. Open the Storage panel in the second tab — confirm the key is not there.
  • Check your storage quota. Run this in the Console: navigator.storage.estimate().then(e => console.log(`${(e.usage/1024/1024).toFixed(2)} MB used of ${(e.quota/1024/1024).toFixed(0)} MB`));
  • Test logout cleanup. Log into a site and note what cookies and localStorage keys exist. Click the logout button. Open the Storage panel — are the session cookie and any user-specific localStorage entries gone? Any that remain after logout are a potential security issue.

📌 Key Takeaways

  • Session cookies must have HttpOnly (no JS access), Secure (HTTPS only), and SameSite=Lax or Strict (CSRF protection).
  • localStorage is persistent and origin-scoped but readable by any JS — never store auth tokens here; use HttpOnly cookies.
  • sessionStorage is identical to localStorage but cleared on tab close — ideal for temporary, tab-specific state like multi-step forms.
  • IndexedDB is a full client-side database for structured data and large volumes; used by PWAs for offline support.
  • Cache Storage is Service Worker-controlled — it stores complete HTTP responses and is the backbone of offline-capable apps.
  • Edit cookie values directly in the Storage panel to test auth flows — set a token to INVALID to verify the server rejects it.
  • After logout, check the Storage panel — all session data should be gone. Leftover tokens in any storage are a security bug.
Up next
Lesson 7 — The Performance Panel: recording profiles, reading the flame chart, identifying long tasks, and diagnosing slow animations