skip to content

Why does Angular's HTTP transfer cache skip requests with Authorization or Cookie headers by default, and when is includeRequestsWithAuthHeaders safe to enable?

level: seniorimportance: should knowfreq 35%

answer

  1. response lands inside the HTML
  2. HTML may be shared or cached
  3. whole body, not just rendered fields
  4. global flag, narrowed by filter

basics

~20 s

Authenticated responses are user-specific, and the transfer cache embeds the whole response body in the HTML. If that HTML is cached or prerendered, one user's data reaches others. Enable the flag only for per-request, non-shared pages, narrowed with filter.

solid answer

~50 s

The transfer cache writes each stored response's full body into a JSON script tag inside the HTML. For a request carrying `Authorization`, `Proxy-Authorization` or `Cookie`, that body is one person's data, often with more fields than the template shows. If the HTML is cached by a CDN or reverse proxy, prerendered at build time, or stored anywhere shared, the next visitor gets it. Since v18 Angular skips such requests by default, and it also skips credentialed requests, `Cache-Control: private`/`no-store`/`no-cache` and `Set-Cookie` responses. Enabling `includeRequestsWithAuthHeaders` is reasonable only when the page renders per request, its HTML response is marked `private` or `no-store` so no shared layer keeps it, and the endpoint returns just the requester's own non-secret data. Because the flag is global, pair it with a `filter` that excludes everything except the few endpoints you have reviewed.

go deeper

for a junior

Remember that requests with auth headers or cookies are not cached by default, because their data belongs to one user.

for a middle

Explain that the whole response body is embedded in the HTML and list the three include flags and what each relaxes.

for a senior

State the conditions for enabling the flag: per-request rendering, private or no-store HTML, own non-secret data, and a filter narrowing it to reviewed endpoints.

for a principal

Set a policy for personal data in server-rendered HTML across render modes and caching layers, and prefer designs that keep it out entirely.

## What the cache actually ships Angular's **HTTP transfer cache** stores an eligible `HttpClient` response on the server and writes it into the page as JSON inside a `<script type="application/json">` tag. The browser reads it during hydration to avoid a second request. Two facts make this security-relevant: - The **entire response body** is embedded, not only the fields the template rendered. An endpoint returning a profile may include an email address, internal ids or flags nobody meant to show. - The HTML is an ordinary document. Whatever layers store HTML (a CDN, a reverse proxy, a prerendered file, a static host) now store that JSON too. ## Why the default excludes auth and cookies A request with `Authorization`, `Proxy-Authorization` or `Cookie` is, almost by definition, about **one visitor**. Embedding its response creates a path for that visitor's data to reach someone else: 1. The server renders the page for user A and embeds A's account data. 2. A shared cache in front of the app stores that HTML, because the HTML response itself did not say `private`. 3. User B requests the same URL and receives A's HTML, including A's JSON. Angular therefore skips these requests unless you opt in; this became the default in v18. Related defaults apply the same reasoning: | Default exclusion | Opt-in flag | |---|---| | `Authorization`, `Proxy-Authorization` or `Cookie` request headers | `includeRequestsWithAuthHeaders` | | `withCredentials`, or Fetch `credentials` of `include` or `same-origin` | `includeRequestsWithCredentials` | | `Cache-Control` of `no-store`, `no-cache` or `private`, `Set-Cookie`, Fetch `cache` of `no-store` or `no-cache` | `includeNonCacheableRequests` | All three flags live in `withHttpTransferCacheOptions()` and apply to the whole app. ## When enabling it is defensible Enable `includeRequestsWithAuthHeaders` only when **all** of these hold: - The route renders **per request** (server rendering), never prerendered at build time or served from an app shell. - The HTML response is sent with `Cache-Control: private` or `no-store`, and you have checked that every layer in front of the app honours it. - The endpoint returns only the requester's **own** data, and nothing in the body is a secret (tokens, other users' data, internal notes). - The saving matters, for example a large dashboard whose second fetch costs a visible delay. Then narrow it. `filter` can only exclude, so turn the flag on and use `filter` to exclude every authenticated request except the reviewed endpoints: ```ts import { HttpRequest } from '@angular/common/http'; import { provideClientHydration, withHttpTransferCacheOptions } from '@angular/platform-browser'; const SAFE_AUTH_ENDPOINTS = ['/api/me/preferences']; const AUTH_HEADERS = ['authorization', 'proxy-authorization', 'cookie']; const isAuthenticated = (req: HttpRequest<unknown>) => AUTH_HEADERS.some((h) => req.headers.has(h)); provideClientHydration( withHttpTransferCacheOptions({ includeRequestsWithAuthHeaders: true, filter: (req) => !isAuthenticated(req) || SAFE_AUTH_ENDPOINTS.some((p) => req.url.endsWith(p)), }), ); ``` The per-request `transferCache` option **cannot** lift the auth exclusion; only the global flag does. ## Safer alternatives - Render the personal part in the browser only, and let the server render the shared shell. The anonymous parts still benefit from the cache. - Split endpoints so the server render calls anonymous, public endpoints, and personal data is fetched after hydration. - Trim the endpoint's response to what the page needs before any caching decision. ## A review checklist before flipping the flag 1. List every authenticated endpoint the server render calls, and read one real response body from each. 2. Confirm the route's render mode is per-request server rendering, not prerendering. 3. Check the HTML response's `Cache-Control` at the origin and after every proxy or CDN hop. 4. Write the `filter` so unknown authenticated endpoints are excluded by default. 5. Add a test that renders the page for two different users and asserts neither page contains the other's data. ## What else to check - Review the cache headers of the **HTML** response, not only of the API. The transfer cache honours the API's `Cache-Control`, but nothing in it controls how your HTML is cached. - Remember that personal data rendered into the template is already in the HTML. The transfer cache adds the raw JSON on top, which is often larger and more revealing. - Re-review the flags whenever a route's render mode changes, for instance from server rendering to prerendering.

  • If the template already renders the user's name into the HTML, what extra risk does the transfer cache add?
    It embeds the raw response body as JSON, which usually carries more than the template shows: email, ids, roles, flags. The rendered markup exposes only displayed fields; the script tag exposes the whole payload to anyone who receives that HTML, including through a shared cache.
  • Can a single request opt into caching despite carrying an Authorization header?
    No. The request-level `transferCache` option can opt out, set `includeHeaders` or admit a single POST, but the auth-header exclusion is lifted only by the global `includeRequestsWithAuthHeaders` flag. The usual pattern is to enable the flag and use `filter` to exclude every authenticated endpoint you have not reviewed.

saying these in an interview costs you the question

  • Only the fields shown in the template end up in the page.
  • The API's Cache-Control header also protects the HTML page from shared caches.
  • transferCache: true on one request safely admits an authenticated call.
  • Enabling includeRequestsWithAuthHeaders only affects performance, never privacy.
  • Prerendered pages are safe for user data because they are built at deploy time.