# Authentication tokens stored securely, not exposed in localStorage

- **Pattern:** `ab-001301` (`extension-data-privacy.data-collection.tokens-not-in-storage`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001301
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001301)

## Why it matters

OAuth tokens and session tokens stored in `localStorage` are readable by any JavaScript running on the page — including third-party scripts, ad networks, and XSS payloads. Because extensions share the DOM with page scripts when using content scripts, a single XSS vulnerability on any site the user visits can silently extract a token and enable full account takeover. OWASP 2021 A02 and CWE-312 both call this out: storing sensitive credentials without isolation is a broken authentication failure. GDPR Art. 32 requires appropriate technical measures to protect personal data — storing auth tokens in `localStorage` fails that standard.

## Severity rationale

Critical because auth tokens in `localStorage` are accessible to any page script, making a single XSS injection sufficient to exfiltrate the token and impersonate the authenticated user across all their sessions.

## Remediation

Move all user authentication tokens from `localStorage` or `sessionStorage` into `chrome.storage.local`, which is sandboxed from page scripts:

```js
// BAD — accessible to any page script
localStorage.setItem('token', response.access_token);

// GOOD — isolated to extension context
chrome.storage.local.set({ authToken: response.access_token });

// BEST — memory only (token lost on extension unload, no persistent attack surface)
let authToken = null;
authToken = response.access_token;
```

For tokens that must persist across sessions, use `chrome.storage.local` with encryption via a library like TweetNaCl.js rather than storing plaintext.

## Detection

- **ID:** `tokens-not-in-storage`
- **Severity:** `critical`
- **What to look for:** First, determine whether the extension has a user authentication flow (login, OAuth, session management). If there is no user auth flow, mark skip. If there is an auth flow, search content scripts and popup/options code for patterns that store user auth tokens in `localStorage` or `sessionStorage`. Look for `localStorage.setItem('token', ...)`, `sessionStorage.token = ...`, or similar. Note: service API keys stored in `chrome.storage.sync` or `chrome.storage.local` are a separate concern handled by the `no-sync-secrets` check — do not evaluate service credentials here.
- **Skip (N/A) when:** The extension does not authenticate users and has no user login/logout flow. Even if the extension stores service API keys or other credentials, mark skip if there are no user-owned authentication tokens to evaluate. Evaluate skip BEFORE pass/fail.
- **Pass criteria:** Count all token storage locations in the codebase. Extension has a user auth flow AND user authentication tokens (OAuth tokens, session tokens, bearer tokens) are NOT stored in `localStorage` or `sessionStorage`. At least 100% of persistent tokens use `chrome.storage.local` or are stored in memory only. Report the count of storage locations found even on pass.
- **Fail criteria:** User authentication tokens found in `localStorage` or `sessionStorage`. These are accessible to any script with DOM access, including injected scripts or XSS exploits.
- **Detail on fail:** Identify the token types and storage location. Example: `"OAuth access token stored in localStorage under key 'auth_token' — accessible to XSS attacks"` or `"API key hardcoded in localStorage and logged in background script."`
- **Remediation:** Move tokens from `localStorage` to `chrome.storage.local` or memory:

  ```js
  // BAD: localStorage
  localStorage.setItem('token', response.access_token);

  // GOOD: chrome.storage.local
  chrome.storage.local.set({ authToken: response.access_token });

  // BETTER: memory-only (lost on unload)
  let authToken = null;
  ```

## External references

- cwe CWE-312
- cwe CWE-922
- owasp A02
- gdpr Art. 32

Taxons: cryptography-and-secrets, access-control

HTML version: https://auditbuffet.com/patterns/ab-001301
