From 48cecfff7f76e9d6087139e6447855c5d50b7dc7 Mon Sep 17 00:00:00 2001 From: Joshua Oosthuizen Date: Mon, 5 Oct 2026 08:56:53 +0200 Subject: [PATCH] Fix magic link redirect to localhost behind a reverse proxy After verifying the link, /auth/confirm built its redirect from request.nextUrl. Behind a proxy or tunnel a route handler sees the address the server is bound to (localhost:4322), so the browser was sent off the public domain and away from the session cookie it had just been given. Build the redirect from NEXT_PUBLIC_SITE_URL instead, the same value the magic link email already uses. Co-Authored-By: Claude Opus 5.5 --- src/app/auth/confirm/route.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/auth/confirm/route.ts b/src/app/auth/confirm/route.ts index 4355ea2..610ae77 100644 --- a/src/app/auth/confirm/route.ts +++ b/src/app/auth/confirm/route.ts @@ -1,6 +1,7 @@ import { NextResponse, type NextRequest } from "next/server"; import { z } from "zod"; +import { serverEnv } from "@/lib/env.server"; import { createClient } from "@/lib/supabase/server"; const otpTypeSchema = z.enum(["magiclink", "email", "signup", "invite", "recovery", "email_change"]); @@ -23,8 +24,9 @@ export async function GET(request: NextRequest) { signedIn = !error; } - const url = request.nextUrl.clone(); - url.pathname = signedIn ? "/admin" : "/login"; - url.search = signedIn ? "" : "?error=link"; - return NextResponse.redirect(url); + // Built from the configured site URL, not request.nextUrl: behind a reverse proxy or + // tunnel a route handler sees the address the server is bound to (e.g. localhost:4322), + // and redirecting there leaves the session cookie behind on the public domain. + const path = signedIn ? "/admin" : "/login?error=link"; + return NextResponse.redirect(`${serverEnv.NEXT_PUBLIC_SITE_URL}${path}`); }