Skip to content

Commit c626eac

Browse files
committed
fix(@angular/ssr): support custom headers in redirect responses
Updates createRedirectResponse to accept an optional Record<string, string> of headers, allowing custom headers to be merged into the redirect response. The Location and Vary: X-Forwarded-Prefix headers are automatically set to ensure correct routing and proxy behavior. AngularServerApp now passes relevant headers from the matched route or response context when creating a redirect.
1 parent e6a2fd3 commit c626eac

File tree

3 files changed

+61
-3
lines changed

3 files changed

+61
-3
lines changed

packages/angular/ssr/src/app.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export class AngularServerApp {
190190
return null;
191191
}
192192

193-
const { redirectTo, status, renderMode } = matchedRoute;
193+
const { redirectTo, status, renderMode, headers } = matchedRoute;
194194

195195
if (redirectTo !== undefined) {
196196
return createRedirectResponse(
@@ -199,6 +199,7 @@ export class AngularServerApp {
199199
buildPathWithParams(redirectTo, url.pathname),
200200
),
201201
status,
202+
headers,
202203
);
203204
}
204205

@@ -352,7 +353,7 @@ export class AngularServerApp {
352353
}
353354

354355
if (result.redirectTo) {
355-
return createRedirectResponse(result.redirectTo, responseInit.status);
356+
return createRedirectResponse(result.redirectTo, responseInit.status, headers);
356357
}
357358

358359
if (renderMode === RenderMode.Prerender) {

packages/angular/ssr/src/utils/redirect.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,14 @@ export function isValidRedirectResponseCode(code: number): boolean {
2727
* @param location - The URL to which the response should redirect.
2828
* @param status - The HTTP status code for the redirection. Defaults to 302 (Found).
2929
* See: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static#status
30+
* @param headers - Additional headers to include in the response.
3031
* @returns A `Response` object representing the HTTP redirect.
3132
*/
32-
export function createRedirectResponse(location: string, status = 302): Response {
33+
export function createRedirectResponse(
34+
location: string,
35+
status = 302,
36+
headers?: Record<string, string>,
37+
): Response {
3338
if (ngDevMode && !isValidRedirectResponseCode(status)) {
3439
throw new Error(
3540
`Invalid redirect status code: ${status}. ` +
@@ -40,7 +45,9 @@ export function createRedirectResponse(location: string, status = 302): Response
4045
return new Response(null, {
4146
status,
4247
headers: {
48+
...headers,
4349
'Location': location,
50+
'Vary': 'X-Forwarded-Prefix',
4451
},
4552
});
4653
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import { createRedirectResponse } from '../../src/utils/redirect';
10+
11+
describe('Redirect Utils', () => {
12+
describe('createRedirectResponse', () => {
13+
it('should create a redirect response with default status 302', () => {
14+
const response = createRedirectResponse('/home');
15+
expect(response.status).toBe(302);
16+
expect(response.headers.get('Location')).toBe('/home');
17+
expect(response.headers.get('Vary')).toBe('X-Forwarded-Prefix');
18+
});
19+
20+
it('should create a redirect response with a custom status', () => {
21+
const response = createRedirectResponse('/home', 301);
22+
expect(response.status).toBe(301);
23+
expect(response.headers.get('Location')).toBe('/home');
24+
});
25+
26+
it('should allow providing additional headers', () => {
27+
const response = createRedirectResponse('/home', 302, { 'X-Custom': 'value' });
28+
expect(response.headers.get('X-Custom')).toBe('value');
29+
expect(response.headers.get('Location')).toBe('/home');
30+
expect(response.headers.get('Vary')).toBe('X-Forwarded-Prefix');
31+
});
32+
33+
it('should override Location and Vary headers if provided in extra headers', () => {
34+
const response = createRedirectResponse('/home', 302, {
35+
'Location': '/evil',
36+
'Vary': 'Host',
37+
});
38+
expect(response.headers.get('Location')).toBe('/home');
39+
expect(response.headers.get('Vary')).toBe('X-Forwarded-Prefix');
40+
});
41+
42+
it('should throw error for invalid redirect status code in dev mode', () => {
43+
// @ts-expect-error accessing global
44+
globalThis.ngDevMode = true;
45+
expect(() => createRedirectResponse('/home', 200)).toThrowError(
46+
/Invalid redirect status code: 200/,
47+
);
48+
});
49+
});
50+
});

0 commit comments

Comments
 (0)