Skip to content

Commit 1caa580

Browse files
fix: correctly set x-forwarded-* in Middleware (#57815)
### What? Follow-up of #56797 While working on this, I noticed that some logic around stripping internal headers was duplicated, so I did some cleanup too. ### Why? In #56797 we set these headers, but it only affected Route Handlers, Middleware is still missing them, which is a regression introduced in #52492 (Related: vercel/next-learn#252) ### How? Move to set these headers up to `base-server.ts` so they are present in Middleware too. > Note: All headers are set with `??=` to respect the original value if set (with other words, only add these headers if they aren't set yet) Closes NEXT- Fixes #52266
1 parent f2efb50 commit 1caa580

File tree

11 files changed

+48
-54
lines changed

11 files changed

+48
-54
lines changed

packages/next/src/server/base-server.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ import getRouteFromAssetPath from '../shared/lib/router/utils/get-route-from-ass
127127
import { stripInternalHeaders } from './internal-utils'
128128
import { RSCPathnameNormalizer } from './future/normalizers/request/rsc'
129129
import { PostponedPathnameNormalizer } from './future/normalizers/request/postponed'
130+
import type { TLSSocket } from 'tls'
130131

131132
export type FindComponentsResult = {
132133
components: LoadComponentsReturnType
@@ -894,6 +895,15 @@ export default abstract class Server<ServerOptions extends Options = Options> {
894895
}
895896
}
896897

898+
req.headers['x-forwarded-host'] ??= `${this.hostname}:${this.port}`
899+
req.headers['x-forwarded-port'] ??= this.port?.toString()
900+
const { originalRequest } = req as NodeNextRequest
901+
req.headers['x-forwarded-proto'] ??= (originalRequest.socket as TLSSocket)
902+
?.encrypted
903+
? 'https'
904+
: 'http'
905+
req.headers['x-forwarded-for'] ??= originalRequest.socket?.remoteAddress
906+
897907
this.attachRequestMeta(req, parsedUrl)
898908

899909
const domainLocale = this.i18nProvider?.detectDomainLocale(

packages/next/src/server/internal-utils.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { IncomingHttpHeaders } from 'http'
22
import type { NextParsedUrlQuery } from './request-meta'
33

44
import { NEXT_RSC_UNION_QUERY } from '../client/components/app-router-headers'
5+
import { INTERNAL_HEADERS } from '../shared/lib/constants'
56

67
const INTERNAL_QUERY_NAMES = [
78
'__nextFallback',
@@ -39,19 +40,6 @@ export function stripInternalSearchParams<T extends string | URL>(
3940
return (isStringUrl ? instance.toString() : instance) as T
4041
}
4142

42-
/**
43-
* Headers that are set by the Next.js server and should be stripped from the
44-
* request headers going to the user's application.
45-
*/
46-
const INTERNAL_HEADERS = [
47-
'x-invoke-path',
48-
'x-invoke-status',
49-
'x-invoke-error',
50-
'x-invoke-query',
51-
'x-invoke-output',
52-
'x-middleware-invoke',
53-
] as const
54-
5543
/**
5644
* Strip internal headers from the request headers.
5745
*

packages/next/src/server/lib/mock-request.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ export class MockedRequest extends Stream.Readable implements IncomingMessage {
3737
private bodyReadable?: Stream.Readable
3838

3939
// If we don't actually have a socket, we'll just use a mock one that
40-
// always returns false for the `encrypted` property.
40+
// always returns false for the `encrypted` property and undefined for the
41+
// `remoteAddress` property.
4142
public socket: Socket = new Proxy<TLSSocket>({} as TLSSocket, {
4243
get: (_target, prop) => {
43-
if (prop !== 'encrypted') {
44+
if (prop !== 'encrypted' && prop !== 'remoteAddress') {
4445
throw new Error('Method not implemented')
4546
}
4647

48+
if (prop === 'remoteAddress') return undefined
4749
// For this mock request, always ensure we just respond with the encrypted
4850
// set to false to ensure there's no odd leakages.
4951
return false

packages/next/src/server/lib/router-server.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import {
3232
PERMANENT_REDIRECT_STATUS,
3333
} from '../../shared/lib/constants'
3434
import { DevBundlerService } from './dev-bundler-service'
35-
import type { TLSSocket } from 'tls'
3635

3736
const debug = setupDebug('next:router-server:main')
3837

@@ -225,17 +224,6 @@ export async function initialize(opts: {
225224
'x-middleware-invoke': '',
226225
'x-invoke-path': invokePath,
227226
'x-invoke-query': encodeURIComponent(JSON.stringify(parsedUrl.query)),
228-
'x-forwarded-host':
229-
req.headers['x-forwarded-host'] ?? req.headers.host ?? opts.hostname,
230-
'x-forwarded-port':
231-
req.headers['x-forwarded-port'] ?? opts.port.toString(),
232-
'x-forwarded-proto':
233-
req.headers['x-forwarded-proto'] ??
234-
(req.socket as TLSSocket).encrypted
235-
? 'https'
236-
: 'http',
237-
'x-forwarded-for':
238-
req.headers['x-forwarded-for'] ?? req.socket.remoteAddress,
239227
...(additionalInvokeHeaders || {}),
240228
}
241229
Object.assign(req.headers, invokeHeaders)

packages/next/src/server/lib/router-utils/proxy-request.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ export async function proxyRequest(
2424
target,
2525
changeOrigin: true,
2626
ignorePath: true,
27-
xfwd: true,
2827
ws: true,
2928
// we limit proxy requests to 30s by default, in development
3029
// we don't time out WebSocket requests to allow proxying
3130
proxyTimeout: proxyTimeout === null ? undefined : proxyTimeout || 30_000,
31+
headers: {
32+
'x-forwarded-host': req.headers.host || '',
33+
},
3234
})
3335

3436
await new Promise((proxyResolve, proxyReject) => {

packages/next/src/server/lib/router-utils/resolve-routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { TLSSocket } from 'tls'
21
import type { FsOutput } from './filesystem'
32
import type { IncomingMessage, ServerResponse } from 'http'
43
import type { NextConfigComplete } from '../../config-shared'
@@ -38,6 +37,7 @@ import {
3837
prepareDestination,
3938
} from '../../../shared/lib/router/utils/prepare-destination'
4039
import { createRequestResponseMocks } from '../mock-request'
40+
import type { TLSSocket } from 'tls'
4141

4242
const debug = setupDebug('next:router-server:resolve-routes')
4343

packages/next/src/server/next-server.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import './node-environment'
22
import './require-hook'
33
import './node-polyfill-crypto'
44

5-
import type { TLSSocket } from 'tls'
65
import type { CacheFs } from '../shared/lib/utils'
76
import {
87
DecodeError,
@@ -41,7 +40,6 @@ import {
4140
SERVER_DIRECTORY,
4241
NEXT_FONT_MANIFEST,
4342
PHASE_PRODUCTION_BUILD,
44-
INTERNAL_HEADERS,
4543
} from '../shared/lib/constants'
4644
import { findDir } from '../lib/find-pages-dir'
4745
import { NodeNextRequest, NodeNextResponse } from './base-http/node'
@@ -1616,10 +1614,6 @@ export default class NextNodeServer extends BaseServer {
16161614
>
16171615
let bubblingResult = false
16181616

1619-
for (const key of INTERNAL_HEADERS) {
1620-
delete req.headers[key]
1621-
}
1622-
16231617
// Strip the internal headers.
16241618
this.stripInternalHeaders(req)
16251619

@@ -1746,11 +1740,8 @@ export default class NextNodeServer extends BaseServer {
17461740
parsedUrl: NextUrlWithParsedQuery,
17471741
isUpgradeReq?: boolean
17481742
) {
1749-
const protocol =
1750-
((req as NodeNextRequest).originalRequest?.socket as TLSSocket)
1751-
?.encrypted || req.headers['x-forwarded-proto']?.includes('https')
1752-
? 'https'
1753-
: 'http'
1743+
// Injected in base-server.ts
1744+
const protocol = req.headers['x-forwarded-proto'] as 'https' | 'http'
17541745

17551746
// When there are hostname and port we build an absolute URL
17561747
const initUrl =

packages/next/src/server/web/adapter.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,7 @@ import { waitUntilSymbol } from './spec-extension/fetch-event'
1111
import { NextURL } from './next-url'
1212
import { stripInternalSearchParams } from '../internal-utils'
1313
import { normalizeRscURL } from '../../shared/lib/router/utils/app-paths'
14-
import {
15-
NEXT_ROUTER_PREFETCH,
16-
NEXT_ROUTER_STATE_TREE,
17-
RSC,
18-
} from '../../client/components/app-router-headers'
14+
import { FLIGHT_PARAMETERS } from '../../client/components/app-router-headers'
1915
import { NEXT_QUERY_PARAM_PREFIX } from '../../lib/constants'
2016
import { ensureInstrumentationRegistered } from './globals'
2117
import { RequestAsyncStorageWrapper } from '../async-storage/request-async-storage-wrapper'
@@ -47,12 +43,6 @@ class NextRequestHint extends NextRequest {
4743
}
4844
}
4945

50-
const FLIGHT_PARAMETERS = [
51-
[RSC],
52-
[NEXT_ROUTER_STATE_TREE],
53-
[NEXT_ROUTER_PREFETCH],
54-
] as const
55-
5646
export type AdapterOptions = {
5747
handler: NextMiddleware
5848
page: string

packages/next/src/shared/lib/constants.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ export const COMPILER_NAMES = {
1010
edgeServer: 'edge-server',
1111
} as const
1212

13+
/**
14+
* Headers that are set by the Next.js server and should be stripped from the
15+
* request headers going to the user's application.
16+
*/
1317
export const INTERNAL_HEADERS = [
14-
'x-invoke-path',
15-
'x-invoke-status',
1618
'x-invoke-error',
19+
'x-invoke-output',
20+
'x-invoke-path',
1721
'x-invoke-query',
22+
'x-invoke-status',
1823
'x-middleware-invoke',
1924
] as const
2025

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { NextResponse } from 'next/server'
2+
3+
export function middleware(req: Request) {
4+
return NextResponse.next({
5+
headers: {
6+
'middleware-x-forwarded-host':
7+
req.headers.get('x-forwarded-host') ?? 'wrong',
8+
'middleware-x-forwarded-port':
9+
req.headers.get('x-forwarded-port') ?? 'wrong',
10+
'middleware-x-forwarded-proto':
11+
req.headers.get('x-forwarded-proto') ?? 'wrong',
12+
},
13+
})
14+
}

0 commit comments

Comments
 (0)