Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 0789a9e

Browse files
alan-agius4dgp1130
authored andcommitted
fix(@angular/ssr): ensure correct Location header for redirects behind a proxy
Previously, when the application was served behind a proxy, server-side redirects generated an incorrect Location header, causing navigation issues. This fix updates `createRequestUrl` to use the port from the Host header, ensuring accurate in proxy environments. Additionally, the Location header now only contains the pathname, improving compliance with redirect handling in such setups. Closes #29151 (cherry picked from commit ad1d7d7)
1 parent 5ebeb37 commit 0789a9e

File tree

3 files changed

+37
-14
lines changed

3 files changed

+37
-14
lines changed

‎packages/angular/ssr/node/src/request.ts‎

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,40 @@ function createRequestUrl(nodeRequest: IncomingMessage | Http2ServerRequest): UR
8383
originalUrl,
8484
} = nodeRequest as IncomingMessage & { originalUrl?: string };
8585
const protocol =
86-
headers['x-forwarded-proto'] ?? ('encrypted' in socket && socket.encrypted ? 'https' : 'http');
87-
const hostname = headers['x-forwarded-host'] ?? headers.host ?? headers[':authority'];
88-
const port = headers['x-forwarded-port'] ?? socket.localPort;
86+
getFirstHeaderValue(headers['x-forwarded-proto']) ??
87+
('encrypted' in socket && socket.encrypted ? 'https' : 'http');
88+
const hostname =
89+
getFirstHeaderValue(headers['x-forwarded-host']) ?? headers.host ?? headers[':authority'];
8990

9091
if (Array.isArray(hostname)) {
9192
throw new Error('host value cannot be an array.');
9293
}
9394

9495
let hostnameWithPort = hostname;
95-
if (port && !hostname?.includes(':')) {
96-
hostnameWithPort += `:${port}`;
96+
if (!hostname?.includes(':')) {
97+
const port = getFirstHeaderValue(headers['x-forwarded-port']);
98+
if (port) {
99+
hostnameWithPort += `:${port}`;
100+
}
97101
}
98102

99103
return new URL(originalUrl ?? url, `${protocol}://${hostnameWithPort}`);
100104
}
105+
106+
/**
107+
* Extracts the first value from a multi-value header string.
108+
*
109+
* @param value - A string or an array of strings representing the header values.
110+
* If it's a string, values are expected to be comma-separated.
111+
* @returns The first trimmed value from the multi-value header, or `undefined` if the input is invalid or empty.
112+
*
113+
* @example
114+
* ```typescript
115+
* getFirstHeaderValue("value1, value2, value3"); // "value1"
116+
* getFirstHeaderValue(["value1", "value2"]); // "value1"
117+
* getFirstHeaderValue(undefined); // undefined
118+
* ```
119+
*/
120+
function getFirstHeaderValue(value: string | string[] | undefined): string | undefined {
121+
return value?.toString().split(',', 1)[0]?.trim();
122+
}

‎packages/angular/ssr/src/app.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,15 @@ export class AngularServerApp {
161161

162162
const { redirectTo, status, renderMode } = matchedRoute;
163163
if (redirectTo !== undefined) {
164-
return Response.redirect(
165-
new URL(buildPathWithParams(redirectTo, url.pathname), url),
164+
return new Response(null, {
166165
// Note: The status code is validated during route extraction.
167166
// 302 Found is used by default for redirections
168167
// See: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static#status
169-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
170-
(status as any) ?? 302,
171-
);
168+
status: status ?? 302,
169+
headers: {
170+
'Location': buildPathWithParams(redirectTo, url.pathname),
171+
},
172+
});
172173
}
173174

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

‎packages/angular/ssr/test/app_spec.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,25 +106,25 @@ describe('AngularServerApp', () => {
106106

107107
it('should correctly handle top level redirects', async () => {
108108
const response = await app.handle(new Request('http://localhost/redirect'));
109-
expect(response?.headers.get('location')).toContain('http://localhost/home');
109+
expect(response?.headers.get('location')).toContain('/home');
110110
expect(response?.status).toBe(302);
111111
});
112112

113113
it('should correctly handle relative nested redirects', async () => {
114114
const response = await app.handle(new Request('http://localhost/redirect/relative'));
115-
expect(response?.headers.get('location')).toContain('http://localhost/redirect/home');
115+
expect(response?.headers.get('location')).toContain('/redirect/home');
116116
expect(response?.status).toBe(302);
117117
});
118118

119119
it('should correctly handle relative nested redirects with parameter', async () => {
120120
const response = await app.handle(new Request('http://localhost/redirect/param/relative'));
121-
expect(response?.headers.get('location')).toContain('http://localhost/redirect/param/home');
121+
expect(response?.headers.get('location')).toContain('/redirect/param/home');
122122
expect(response?.status).toBe(302);
123123
});
124124

125125
it('should correctly handle absolute nested redirects', async () => {
126126
const response = await app.handle(new Request('http://localhost/redirect/absolute'));
127-
expect(response?.headers.get('location')).toContain('http://localhost/home');
127+
expect(response?.headers.get('location')).toContain('/home');
128128
expect(response?.status).toBe(302);
129129
});
130130

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /