From 08ba965921da148ee1792ffd699479bfe7dca8ca Mon Sep 17 00:00:00 2001 From: Matt <9137721+program-the-brain-not-the-heartbeat@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:59:01 -0400 Subject: [PATCH] fix: handle x-forwarded-host and proxy issues behind Cloudflare (#370) --- apps/web/src/pages/api/auth/[...all].ts | 17 +++++++++++++++++ packages/auth/src/auth.ts | 15 ++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/web/src/pages/api/auth/[...all].ts b/apps/web/src/pages/api/auth/[...all].ts index 7b4ecb52..46e78897 100644 --- a/apps/web/src/pages/api/auth/[...all].ts +++ b/apps/web/src/pages/api/auth/[...all].ts @@ -13,6 +13,23 @@ const authHandler = toNodeHandler(auth.handler); export default withRateLimit( { points: 100, duration: 60 }, async (req, res) => { + /** + * Better-auth behind proxies (Nginx/Cloudflare) can sometimes fail to parse the protocol + * if headers are incorrectly set or if there are multiple values in X-Forwarded-Proto. + * We sanitize these headers here to ensure better-auth gets a clean protocol and host. + */ + const forwardedProto = req.headers["x-forwarded-proto"]; + if (forwardedProto) { + const p = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto; + req.headers["x-forwarded-proto"] = p?.split(",")[0]?.trim(); + } + + const forwardedHost = req.headers["x-forwarded-host"]; + if (forwardedHost) { + const h = Array.isArray(forwardedHost) ? forwardedHost[0] : forwardedHost; + req.headers["host"] = h?.split(",")[0]?.trim(); + } + return await authHandler(req, res); }, ); diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts index 4eec3d70..fed3b644 100644 --- a/packages/auth/src/auth.ts +++ b/packages/auth/src/auth.ts @@ -11,15 +11,16 @@ import { createPlugins } from "./plugins"; import { configuredProviders } from "./providers"; export const initAuth = (db: dbClient) => { + const baseURL = env("NEXT_PUBLIC_BASE_URL") || env("BETTER_AUTH_URL"); + const trustedOrigins = env("BETTER_AUTH_TRUSTED_ORIGINS")?.split(",") ?? []; + return betterAuth({ secret: env("BETTER_AUTH_SECRET"), - baseURL: env("NEXT_PUBLIC_BASE_URL"), - trustedOrigins: env("BETTER_AUTH_TRUSTED_ORIGINS") - ? [ - env("NEXT_PUBLIC_BASE_URL") ?? "", - ...(env("BETTER_AUTH_TRUSTED_ORIGINS")?.split(",") ?? []), - ] - : [env("NEXT_PUBLIC_BASE_URL") ?? ""], + baseURL, + trustedOrigins: [ + ...(baseURL ? [baseURL] : []), + ...trustedOrigins, + ], database: drizzleAdapter(db, { provider: "pg", schema: {