Caching & Vary

Two distinct caching lessons. First: a passed preflight is expensive, so the browser remembers it — repeat the same request and the OPTIONS round trip is silently skipped for a while. Second: a shared cache (a CDN, a platform edge) sitting in front of an origin-reflecting API can leak one visitor’s authorised response to another unless it varies on Origin. The probe below measures the first one live; the second is simulated, because no real shared cache sits in front of this demo API.

The preflight cache, measured live

Press Run series to fire 5 identical cross-origin PUT requests at one fixed URL and count how many OPTIONS preflights the server actually received. The browser decides on its own whether to reuse a cached preflight — the counts below come from the server's own record.

Vary: Origin — the shared-cache trap

The demo API reflects the requesting origin back in Access-Control-Allow-Origin (an allowlist-gated match, never a blind echo). That is the correct, safe pattern for a single-origin API. But put a shared cache between the browser and that API — a CDN keyed on URL alone — and the cache will happily store one visitor’s ACAO: https://app-one.example response and serve it to a different visitor whose origin is https://app-two.example. The second visitor’s browser then reads an ACAO that does not match its own origin and blocks the read: one user’s authorisation leaked into another’s request. Vary: Origin is the header that prevents it — it instructs the cache to keep each origin’s entry separate.

Step through the simulated version below. It is labelled simulated because, again, no real CDN sits in front of this API — the failure it walks through is a real production trap that a single-origin lab cannot reproduce live.

Simulated — no real CDN sits in front of this API

A shared cache (a CDN, a platform edge) that keys only on URL will hand one visitor’s authorised response to a different visitor whose origin the server never approved. Vary: Origin is the fix — it tells the cache to keep each origin’s entry separate. This widget steps through the failure with two fictional origins; no real request leaves your browser.

Simulated CDN cache slot

empty — nothing cached yet

Press “Replay https://app-one.example’s request” to populate the cache.