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:
- no-cache vs no-store: When to Use Each — the baseline distinction between “revalidate before serving” and “never store.”
- Public vs private cache scope — how
publicandprivatecontrol which cache tiers may retain a response at all. - Header stacking and directive precedence — how multiple directives on one response combine and which one governs when they overlap.
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 respectno-storemay still respectprivateand 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 predateCache-Controlentirely may still checkPragma. On any RFC 9111-compliant cache it is redundant withno-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 violatingno-store, or caching only the response for retry logic) that responses must never be shared across differentAuthorizationheader values. This is a defense-in-depth measure, not a substitute forno-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):
- Open Chrome DevTools → Application tab → Back/forward cache section.
- Navigate to the sensitive page, then away to another page.
- Click the browser’s Back button, then re-run the bfcache diagnostic in DevTools.
- Confirm the report shows the page was not restored from bfcache, with a reason similar to “the response had a
Cache-Control: no-storeheader.”
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
Ageheader appears on any response, on any repeated request, from any cache tier — its presence indicates a storage violation. CF-Cache-Status: DYNAMICor an equivalent non-cached status appears on every request;HITat any point is a leak.- Two different
Authorizationtokens 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-storeif 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-Controlheader is even evaluated, which can transiently buffer a response. Confirm your CDN’s documented behavior forno-storespecifically, not just its default cache rules. - Missing
Vary: Authorizationon a technically no-store-compliant cache — if an intermediary is only partially compliant and stores responses for retry or coalescing purposes despiteno-store, the absence ofVary: Authorizationis 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-storedoes 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.
Related
- Public vs private cache scope — the scope directive to reach for when data is user-specific but not independently dangerous if cached to disk.
- no-cache vs max-age=0: What’s the Difference? — covers the revalidation-focused directive pair, one tier less strict than
no-store. - Cache-Control best practices for REST APIs — broader guidance on directive selection across an API’s full response surface.
- Using
Vary: Accept-Encodingwithout fragmenting cache — background on howVaryvalues affect cache key generation at the edge.