An API endpoint returns data that differs per authenticated user. What has to be true before any shared cache such as a CDN or reverse proxy may store that response, and how do you design so one user's data can never be served to another?
answer
- private cache = one user; shared = everyone
- Authorization rule does not cover cookies or API keys
- identity in the path → collision unrepresentable
- Vary: Cookie = fragmentation + false safety
- CDN default deny, opt in per route
basics
~20 sA shared cache must not store an authenticated response unless the response explicitly permits shared storage. Safer than tuning directives: separate public resources from per-user ones, put the user identity in the URL path, and mark per-user responses as storable only by that user's own client.
solid answer
~1 minTwo kinds of cache: a **private** cache belongs to one user (browser, mobile app store), a **shared** cache serves many (CDN, reverse proxy, corporate proxy). HTTP already protects you a little — a shared cache must not reuse a response to a request carrying an `Authorization` header unless the response explicitly allows it — but that guarantee evaporates the moment auth moves to a cookie or a custom header, which is where most real leaks come from. So I do not rely on directives alone. Design rules: 1. **Split resources.** Public catalogue data and personalized data live on different endpoints, so the personalized one simply never gets a shared-cache policy. 2. **Put identity in the path** — `/v1/users/{id}/orders`, not `/v1/orders` keyed off the token — so two users can never collide on one cache key even if something upstream misbehaves. 3. **Mark per-user responses private.** Explicitly storable by the client only. 4. **Treat `Vary` on cookies as a smell.** It fragments the cache to near-zero hit rate and is fragile; if you truly need per-token variance at the edge, put the identity in the cache key deliberately. 5. **Default deny at the CDN** — caching enabled per route, never a blanket TTL.
code
http · 10 linesGET /v1/products/42 HTTP/1.1
HTTP/1.1 200 OK
Cache-Control: public, max-age=60, s-maxage=300
GET /v1/users/8817/orders HTTP/1.1
Authorization: Bearer <token>
HTTP/1.1 200 OK
Cache-Control: private, max-age=0, must-revalidatego deeper
Distinguish private from shared caches and know that per-user responses must be marked as storable only by the client that received them.
Explain the Authorization-header rule and why cookie or API-key auth falls outside it, plus what Vary does to the cache key.
Design for structural safety — identity in the path, split public and personalized resources, CDN default deny — and describe how you would test it end to end.
Weigh edge personalization as a deliberate, proof-obligated exercise against the blast radius of a cross-user leak, and set platform defaults that make the unsafe configuration hard to reach.
## Private versus shared A **private cache** is dedicated to a single user: the browser's HTTP cache, an app's on-disk store. A **shared cache** stores responses for many users: a CDN edge, a reverse proxy in front of your service, a corporate forward proxy. The distinction is the whole safety question. A stale response in a private cache is a freshness bug; the same response in a shared cache can be a data breach. HTTP's built-in protection: a shared cache must not store a response to a request that included an `Authorization` header unless the response explicitly permits shared storage. That covers bearer-token APIs using the standard header — and nothing else. Cookie-based sessions, `X-Api-Key`, a token in a query parameter: none of them trigger that rule. The cache sees an ordinary GET, sees a response that looks storable, and stores it under a key that does not include the identity. The next user with the same URL gets the first user's data. This is one of the most common serious CDN misconfigurations in the wild. ## Designing so the leak is impossible, not merely forbidden **Separate the resources.** The most robust structure is to keep public data and personalized data on different endpoints. `/v1/products/42` is identical for everyone and can be cached hard at the edge; `/v1/me/recommendations` is per-user and is never given a shared-cache policy. A single endpoint that returns a public payload with a personalized fragment spliced in ("your price", "in your cart") forfeits all edge caching for the public 95% of the bytes. Composition on the client — fetch the cacheable resource plus a small personalized one — usually beats one personalized mega-response. **Make identity part of the cache key structurally.** A URL like `/v1/users/{userId}/orders` cannot collide across users, because the key differs. `/v1/orders` resolved from the token has one key for everybody, and its correctness depends entirely on nobody ever enabling caching for that route. Structural safety beats configuration safety, especially when the configuration lives in a different team's CDN console. **Label per-user responses private.** Say explicitly that only the requesting client may store the response. This is a belt-and-braces measure that also protects against intermediaries you did not know were in the path — corporate proxies, ISP caches on plaintext HTTP, buggy client libraries. **Understand what `Vary` can and cannot do.** `Vary` tells a cache which request headers form part of the key. In principle `Vary: Authorization` makes per-user entries safe. In practice it is a poor tool for personalization: every distinct token gets its own entry, so the hit rate collapses toward zero while storage explodes; `Vary: Cookie` is worse, because cookie strings carry analytics values that differ per browser, meaning near-total cache fragmentation and a false sense of safety if any path ever drops the header. Treat `Vary` as necessary for genuine content negotiation and as a warning sign when used for identity. **Default deny at the edge.** CDN configuration should cache nothing unless a route is explicitly opted in. The failure mode of the opposite default — blanket TTL with exceptions — is that any new endpoint is cached before anyone thinks about it, and the first personalized endpoint added after that leaks. ## Verifying it This is one of the few caching properties worth an automated test. Fetch a personalized endpoint as user A through the real edge, then as user B with a fresh connection, and assert the payloads differ and the response indicates a miss or a private policy. Also assert that no personalized route ever returns a header claiming shared cacheability. In incident review, the diagnostic signature is unmistakable: a burst of "I can see someone else's account" reports that correlates with cache hit ratio and disappears after a purge — by which time the wrong data has already been served. ## The judgement call Edge caching of authenticated responses is not forbidden — some high-scale systems do it deliberately by making the identity an explicit part of the cache key at the edge, and it works. But it is an opt-in engineering exercise with a proof obligation, not a default. For most APIs the right answer is: personalize as little as possible, keep the cacheable parts public and shared, keep everything user-specific private, and let the URL structure make a collision unrepresentable.
- Why is Vary: Cookie a weak way to make an authenticated response safe in a shared cache?Cookie strings vary far beyond identity — analytics, consent and A/B values differ per browser — so every visitor gets a distinct cache entry and the hit rate collapses while storage grows. It is also fragile: any hop that normalizes or strips the header, or any code path that forgets the Vary, turns a fragmented cache into a leaking one.
- Your API uses a session cookie rather than the Authorization header. What changes about shared-cache safety?You lose HTTP's built-in protection, which only applies to requests carrying Authorization. A shared cache sees an ordinary GET with a storable-looking response and may cache it under a key with no identity in it. You must therefore mark such responses private explicitly and make the CDN deny caching for those routes by default.
A private cache is your own desk drawer; a shared cache is the pigeonhole wall in the lobby. Anything that goes in the wall needs the recipient's name to be part of the slot, not written on the letter inside.
saying these in an interview costs you the question
- Assuming any authenticated response is automatically safe from shared caches
- Relying on Vary: Cookie as the primary defence for per-user data
- Enabling a blanket CDN TTL across all API routes and exempting problem endpoints later
- Mixing a personalized fragment into an otherwise public response and expecting edge hits
- Believing HTTPS prevents intermediary caching, so no policy is needed