diff --git a/adev/src/content/guide/ssr.md b/adev/src/content/guide/ssr.md index de2ccb671ca..1bfffb5499a 100644 --- a/adev/src/content/guide/ssr.md +++ b/adev/src/content/guide/ssr.md @@ -362,7 +362,7 @@ To configure this, update your `angular.json` file as follows: You can customize how Angular caches HTTP responses during server‑side rendering (SSR) and reuses them during hydration by configuring `HttpTransferCacheOptions`. This configuration is provided globally using `withHttpTransferCacheOptions` inside `provideClientHydration()`. -By default, `HttpClient` caches all `HEAD` and `GET` requests which don't contain `Authorization`, `Proxy-Authorization`, or `Cookie` headers and are not sent with `withCredentials`. You can override those settings by using `withHttpTransferCacheOptions` to the hydration configuration. +By default, `HttpClient` caches all `HEAD` and `GET` requests which don't contain `Authorization`, `Proxy-Authorization`, or `Cookie` headers and are not sent with `withCredentials` or Fetch API `credentials` modes that can send credentials. You can override those settings by using `withHttpTransferCacheOptions` to the hydration configuration. ```ts import { bootstrapApplication } from '@angular/platform-browser'; @@ -417,7 +417,7 @@ Use this only when `POST` requests are **idempotent** and safe to reuse between ### `includeRequestsWithAuthHeaders` Determines whether requests containing `Authorization`, `Proxy‑Authorization`, or `Cookie` headers are eligible for caching. -By default, these are excluded to prevent caching user‑specific responses. Requests sent with `withCredentials` are also excluded by default. +By default, these are excluded to prevent caching user‑specific responses. Requests sent with `withCredentials` or Fetch API `credentials` set to `include` or `same-origin` are also excluded by default. ```ts withHttpTransferCacheOptions({ diff --git a/packages/common/http/src/transfer_cache.ts b/packages/common/http/src/transfer_cache.ts index 689a4e2ea02..98f1a834b4f 100644 --- a/packages/common/http/src/transfer_cache.ts +++ b/packages/common/http/src/transfer_cache.ts @@ -42,7 +42,8 @@ import {HttpParams} from './params'; * (for example using GraphQL). * @param includeRequestsWithAuthHeaders Enables caching of requests containing `Authorization`, * `Proxy-Authorization`, or `Cookie` headers. By default, these requests are excluded from - * caching. Requests sent using `withCredentials` are also excluded by default. + * caching. Requests sent using `withCredentials` or Fetch API `credentials` modes that can send + * credentials are also excluded by default. * * @see [Configuring the caching options](guide/ssr#configuring-the-caching-options) * @@ -136,7 +137,7 @@ export function transferCacheInterceptorFn( !isCacheActive || requestOptions === false || // Do not cache requests sent with credentials. - req.withCredentials || + hasOutgoingCredentials(req) || // POST requests are allowed either globally or at request level (requestMethod === 'POST' && !globalOptions.includePostRequests && !requestOptions) || (requestMethod !== 'POST' && !ALLOWED_METHODS.includes(requestMethod)) || @@ -253,6 +254,10 @@ function hasAuthHeaders(req: HttpRequest): boolean { ); } +function hasOutgoingCredentials(req: HttpRequest): boolean { + return req.withCredentials || req.credentials === 'include' || req.credentials === 'same-origin'; +} + function getFilteredHeaders( headers: HttpHeaders, includeHeaders: string[] | undefined, diff --git a/packages/common/http/test/transfer_cache_spec.ts b/packages/common/http/test/transfer_cache_spec.ts index 0d359087c6c..64537e715be 100644 --- a/packages/common/http/test/transfer_cache_spec.ts +++ b/packages/common/http/test/transfer_cache_spec.ts @@ -41,6 +41,7 @@ interface RequestParams { transferCache?: {includeHeaders: string[]} | boolean; headers?: {[key: string]: string}; withCredentials?: boolean; + credentials?: RequestCredentials; body?: RequestBody; } @@ -408,6 +409,36 @@ describe('TransferCache', () => { }); }); + it('should not cache requests with included credentials', async () => { + makeRequestAndExpectOne('/test-auth', 'foo', { + credentials: 'include', + }); + + makeRequestAndExpectOne('/test-auth', 'foo', { + credentials: 'include', + }); + }); + + it('should not cache requests with same-origin credentials', async () => { + makeRequestAndExpectOne('/test-auth', 'foo', { + credentials: 'same-origin', + }); + + makeRequestAndExpectOne('/test-auth', 'foo', { + credentials: 'same-origin', + }); + }); + + it('should cache requests with omitted credentials', async () => { + makeRequestAndExpectOne('/test-auth', 'foo', { + credentials: 'omit', + }); + + makeRequestAndExpectNone('/test-auth', 'GET', { + credentials: 'omit', + }); + }); + it('should cache POST with the differing body in string form', () => { makeRequestAndExpectOne('/test-1', null, {method: 'POST', transferCache: true, body: 'foo'}); makeRequestAndExpectNone('/test-1', 'POST', {transferCache: true, body: 'foo'}); @@ -570,6 +601,16 @@ describe('TransferCache', () => { }); }); + it(`should not cache requests with included credentials when 'includeRequestsWithAuthHeaders' is 'true'`, async () => { + makeRequestAndExpectOne('/test-auth', 'foo', { + credentials: 'include', + }); + + makeRequestAndExpectOne('/test-auth', 'foo', { + credentials: 'include', + }); + }); + it('should cache a POST request', () => { makeRequestAndExpectOne('/include?foo=1', 'post-body', {method: 'POST'});