Security Resources

⌘K
  1. Home
  2. Security Resources
  3. Secure Code Explain
  4. Sensitive Data Stored in Browser Storage

Sensitive Data Stored in Browser Storage

Here is an example of JavaScript code that is vulnerable to sensitive data stored in browser storage:

🥺 Vulnerable Code

const res = await fetch("/api/login", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(credentials),
});
const data = await res.json();

// Vulnerable: tokens and personal data parked where any script can read them
localStorage.setItem("access_token", data.accessToken);
localStorage.setItem("refresh_token", data.refreshToken);
localStorage.setItem("user", JSON.stringify(data.profile));

localStorage is readable by every script running on the origin. One XSS payload, one compromised npm dependency, one tag manager script gone bad, and two lines of JavaScript ship the refresh token to an attacker's server. Storage also survives tab closes and browser restarts, so a long-lived refresh token on a shared machine is account takeover waiting to happen.

😎 Secure Code

Here is a version of the same code that is secured against sensitive data stored in browser storage:

// Tokens live in cookies the page's JavaScript cannot read
await fetch("/api/login", {
  method: "POST",
  credentials: "same-origin",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(credentials),
});

// The server replies with:
//   Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=900
//   Set-Cookie: refresh=...; HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh

const profile = await (await fetch("/api/me", { credentials: "same-origin" })).json();
renderProfile(profile);          // kept in memory for this page load only

HttpOnly puts the token out of reach of injected scripts, Secure keeps it off plaintext connections, and SameSite blocks the obvious cross-site replay. Scoping the refresh cookie to its own path means it is not even sent on ordinary API calls, and rotating it on every use makes theft detectable. Add a CSRF token for state-changing requests, keep profile data in memory, and clear it on logout.