When to Use no-store for Sensitive API Responses

An API endpoint that returns an authentication token, a full profile with personally identifiable information, or an account balance cannot rely on no-cache or private alone — both still permit the response body to be written to disk or memory somewhere in the request chain. When the data itself is the risk, not just its freshness, no-store is the only directive that removes storage as a possibility.

Prerequisite Concepts

Before configuring sensitive endpoints, be comfortable with:

Step-by-Step Resolution

Step 1 — Identify which responses actually require no-store

Not every authenticated response needs no-store. Reserve it for responses whose body content itself is sensitive if persisted anywhere, including the requesting user’s own device:

  • Authentication tokens, session identifiers, one-time codes, and password-reset links.
  • Full personally identifiable information (PII) — government IDs, dates of birth, full addresses.
  • Financial data — account balances, transaction histories, card details.
  • Any response served over an assumption of “seen once, then gone,” such as a document requiring destruction after viewing.

Responses that are merely user-specific but not independently dangerous if cached to disk (a user’s public display name, a non-sensitive preferences object) are usually adequately served by private with a short max-age, without paying the full cost of no-store on every request.

Step 2 — Set the full header combination on the response

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store, private
Pragma: no-cache
Vary: Authorization

Each piece has a distinct job:

  • no-store — the primary directive. RFC 9111 §5.2.2.5 mandates that no cache may store any part of this response, including the headers, at any tier: browser memory, browser disk, CDN edge, reverse proxy, or corporate gateway.
  • private — belt-and-suspenders scope restriction. Some older or misconfigured intermediaries that do not fully respect no-store may still respect private and refuse to share the response across users; it costs nothing to include both.
  • Pragma: no-cache — a legacy HTTP/1.0 fallback. Ancient proxies that predate Cache-Control entirely may still check Pragma. On any RFC 9111-compliant cache it is redundant with no-store, but it is free insurance for older infrastructure you don’t control.
  • Vary: Authorization — tells any cache that does exist in front of the origin (even one violating no-store, or caching only the response for retry logic) that responses must never be shared across different Authorization header values. This is a defense-in-depth measure, not a substitute for no-store.

Step 3 — Verify no storage occurs at any tier

curl — confirm no Age header ever appears:

curl -sI https://api.example.com/account/balance \
  -H "Authorization: Bearer $TOKEN" \
  | grep -iE '(cache-control|pragma|vary|age|cf-cache-status)'

Expected output:

HTTP/1.1 200 OK
Cache-Control: no-store, private
Pragma: no-cache
Vary: Authorization
CF-Cache-Status: DYNAMIC

CF-Cache-Status: DYNAMIC (Cloudflare) confirms the edge never attempted to store the response. No Age header should ever appear on any repeated request — its presence would mean a cache tier is retaining the response in violation of no-store.

Repeat with a second token to confirm no cross-user bleed:

curl -sI https://api.example.com/account/balance -H "Authorization: Bearer $TOKEN_A" | grep -i cf-cache-status
curl -sI https://api.example.com/account/balance -H "Authorization: Bearer $TOKEN_B" | grep -i cf-cache-status

Both requests must independently show DYNAMIC (or an equivalent “not served from cache” signal). If either shows HIT, a shared cache in the path is storing and reusing a response across distinct users — an active data leak that must be fixed immediately, either by correcting no-store handling upstream or by confirming Vary: Authorization is actually being honored by that layer.

Step 4 — Check back/forward cache exclusion in the browser

For a full HTML page (not just a JSON API response) carrying sensitive data, confirm it is excluded from the browser’s back/forward cache (bfcache):

  1. Open Chrome DevTools → Application tab → Back/forward cache section.
  2. Navigate to the sensitive page, then away to another page.
  3. Click the browser’s Back button, then re-run the bfcache diagnostic in DevTools.
  4. Confirm the report shows the page was not restored from bfcache, with a reason similar to “the response had a Cache-Control: no-store header.”
HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: no-store, private

If DevTools instead shows the page was served from bfcache, verify that no-store is present on the actual document response the browser navigated to (not just on a subordinate API call made from that page) — bfcache eligibility is evaluated per top-level navigation response.

Expected Output / Verification

  • No Age header appears on any response, on any repeated request, from any cache tier — its presence indicates a storage violation.
  • CF-Cache-Status: DYNAMIC or an equivalent non-cached status appears on every request; HIT at any point is a leak.
  • Two different Authorization tokens against the same endpoint never receive each other’s response bodies.
  • Chrome DevTools’ back/forward cache diagnostic explicitly reports the page as ineligible due to no-store, confirming the browser will not silently restore sensitive state from memory after navigation.

Edge Cases

  • Shared-cache leakage from proxy misconfiguration — a reverse proxy or corporate gateway that does not fully implement RFC 9111 may cache a response despite no-store if it was configured with a blanket “cache everything” rule at the infrastructure level, overriding origin headers. Audit intermediary configuration directly; header correctness at the origin is necessary but not sufficient.
  • CDN edge functions caching before header inspection — some CDN compute platforms execute request-collapsing or edge logic before the origin’s Cache-Control header is even evaluated, which can transiently buffer a response. Confirm your CDN’s documented behavior for no-store specifically, not just its default cache rules.
  • Missing Vary: Authorization on a technically no-store-compliant cache — if an intermediary is only partially compliant and stores responses for retry or coalescing purposes despite no-store, the absence of Vary: Authorization is what allows that stored copy to be replayed to a different user. Always set both directives together for defense in depth.
  • bfcache and subresources — excluding the top-level document from bfcache via no-store does not automatically clear sensitive data held in JavaScript memory or IndexedDB; those require explicit application-level cleanup on navigation away from the page.

Frequently Asked Questions

Is private enough for sensitive API responses, or do I need no-store?

private only restricts storage to single-user caches, such as the browser cache — it still permits storage. For data that must never persist to disk or memory anywhere, including in the requesting browser, no-store is required. Use private alone only for data that is safe to keep in the user’s own browser cache but not in a shared CDN.

Why add Pragma: no-cache alongside Cache-Control: no-store?

Pragma: no-cache is a legacy HTTP/1.0 directive that some very old caches and proxies still honor when they do not fully implement Cache-Control. It provides a compatibility fallback and costs nothing to include, though on any RFC 9111-compliant cache it is redundant with no-store.

Does no-store prevent the browser back/forward cache from storing the page?

For the main document response, yes in most current browsers — a no-store response is excluded from bfcache, forcing a full reload on back navigation. This is a deliberate trade-off: it prevents a sensitive page from being restored from memory after the user has navigated away, at the cost of losing the instant back-navigation performance benefit.

Can a shared cache leak a no-store response to a different user?

No, if no-store is honored correctly, because nothing is ever stored to serve back to anyone. The leakage risk instead comes from omitting no-store and relying on private or no-cache alone combined with a missing Vary: Authorization, which can let a misconfigured shared cache key requests incorrectly and serve one user’s response to another.


Back to no-cache vs no-store: When to Use Each