From 2e264014b641bda7b54ed70d71249cbf5d18ab45 Mon Sep 17 00:00:00 2001 From: jerick Date: Mon, 20 Apr 2026 16:44:11 -0400 Subject: [PATCH] fix: derive redirect and origin check from Host header req.url resolves to the internal hostname in Docker standalone mode. Read the Host header directly so redirects and CSRF origin checks use whatever host the browser actually used (IP, hostname, or domain). Co-Authored-By: Claude Sonnet 4.6 --- src/middleware.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index 0a48526..bf12286 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -33,10 +33,22 @@ function isRateLimited(ip: string): boolean { function hasValidOrigin(req: NextRequest): boolean { const origin = req.headers.get('origin') if (!origin) return true // non-browser (curl, server-to-server) - const expected = process.env.NEXTAUTH_URL - ? new URL(process.env.NEXTAUTH_URL).origin - : req.nextUrl.origin - return origin === expected + // Accept the host the browser actually used to reach this server. + const host = req.headers.get('x-forwarded-host') ?? req.headers.get('host') + const proto = req.headers.get('x-forwarded-proto') ?? 'http' + const requestOrigin = host ? `${proto}://${host}` : null + if (requestOrigin && origin === requestOrigin) return true + // Also accept the statically configured NEXTAUTH_URL origin (reverse-proxy setups). + if (process.env.NEXTAUTH_URL && origin === new URL(process.env.NEXTAUTH_URL).origin) return true + return false +} + +// Build an absolute URL using the Host header the browser sent, not the +// internal hostname Next.js resolves to inside Docker. +function siteUrl(req: NextRequest, path: string): URL { + const host = req.headers.get('x-forwarded-host') ?? req.headers.get('host') ?? 'localhost:3000' + const proto = req.headers.get('x-forwarded-proto') ?? 'http' + return new URL(path, `${proto}://${host}`) } export default auth((req) => { @@ -72,13 +84,13 @@ export default auth((req) => { if (pathname.startsWith('/api/')) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } - const loginUrl = new URL('/login', req.url) + const loginUrl = siteUrl(req, '/login') loginUrl.searchParams.set('callbackUrl', pathname) return NextResponse.redirect(loginUrl) } if (pathname === '/login') { - return NextResponse.redirect(new URL('/dashboard', req.url)) + return NextResponse.redirect(siteUrl(req, '/dashboard')) } return NextResponse.next()