From 049bb719e8eb459275d287bbdb41151006025344 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 15 Mar 2026 16:47:27 +0000 Subject: [PATCH] fix(auth): add CORS headers to BetterAuth raw HTTP handler (#112) Co-authored-by: Jason Woltje Co-committed-by: Jason Woltje --- apps/gateway/src/auth/auth.controller.ts | 30 ++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/gateway/src/auth/auth.controller.ts b/apps/gateway/src/auth/auth.controller.ts index ef458fa..f7ec539 100644 --- a/apps/gateway/src/auth/auth.controller.ts +++ b/apps/gateway/src/auth/auth.controller.ts @@ -7,16 +7,17 @@ import { AUTH } from './auth.tokens.js'; export function mountAuthHandler(app: NestFastifyApplication): void { const auth = app.get(AUTH); const nodeHandler = toNodeHandler(auth); + const corsOrigin = process.env['GATEWAY_CORS_ORIGIN'] ?? 'http://localhost:3000'; const fastify = app.getHttpAdapter().getInstance(); - // Use Fastify's addHook to intercept auth requests at the raw HTTP level, - // before Fastify's body parser runs. This avoids conflicts with NestJS's - // custom content-type parser. + // BetterAuth is mounted at the raw HTTP level via Fastify's onRequest hook, + // bypassing NestJS middleware (including CORS). We must set CORS headers + // manually on the raw response before handing off to BetterAuth. fastify.addHook( 'onRequest', ( - req: { raw: IncomingMessage; url: string }, + req: { raw: IncomingMessage; url: string; method: string }, reply: { raw: ServerResponse; hijack: () => void }, done: () => void, ) => { @@ -25,6 +26,27 @@ export function mountAuthHandler(app: NestFastifyApplication): void { return; } + const origin = req.raw.headers.origin; + const allowed = corsOrigin.split(',').map((o) => o.trim()); + + if (origin && allowed.includes(origin)) { + reply.raw.setHeader('Access-Control-Allow-Origin', origin); + reply.raw.setHeader('Access-Control-Allow-Credentials', 'true'); + reply.raw.setHeader( + 'Access-Control-Allow-Methods', + 'GET, POST, PUT, PATCH, DELETE, OPTIONS', + ); + reply.raw.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Cookie'); + } + + // Handle preflight + if (req.method === 'OPTIONS') { + reply.hijack(); + reply.raw.writeHead(204); + reply.raw.end(); + return; + } + reply.hijack(); nodeHandler(req.raw as IncomingMessage, reply.raw as ServerResponse) .then(() => {