From fc0ad0341107635b00a2168b7e0ae8eacb036893 Mon Sep 17 00:00:00 2001 From: RainbowBird Date: Mon, 13 Apr 2026 18:09:33 +0800 Subject: [PATCH] feat(server): require BETTER_AUTH_SECRET and pass it explicitly to betterAuth Without a stable secret, better-auth generates a random one per process, which invalidates every session cookie and JWKS private key on redeploy and across multi-instance deployments. Make it a required env var and wire it into betterAuth({ secret }) explicitly so missing config fails fast at boot instead of silently rotating keys. --- apps/server/.env | 18 ++++++++++-------- apps/server/src/libs/auth.ts | 2 ++ apps/server/src/libs/env.test.ts | 1 + apps/server/src/libs/env.ts | 5 +++++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/server/.env b/apps/server/.env index 3d8b381ee..716d12b75 100644 --- a/apps/server/.env +++ b/apps/server/.env @@ -1,16 +1,18 @@ DATABASE_URL="postgresql://postgres:example-PAssw0rd-xHjDYR.b7N@db:5432/postgres" REDIS_URL="redis://localhost:6379" -AUTH_GOOGLE_CLIENT_ID="change-me" -AUTH_GOOGLE_CLIENT_SECRET="change-me" +BETTER_AUTH_SECRET="" -AUTH_GITHUB_CLIENT_ID="change-me" -AUTH_GITHUB_CLIENT_SECRET="change-me" +AUTH_GOOGLE_CLIENT_ID="" +AUTH_GOOGLE_CLIENT_SECRET="" -STRIPE_SECRET_KEY="change-me" -STRIPE_WEBHOOK_SECRET="change-me" +AUTH_GITHUB_CLIENT_ID="" +AUTH_GITHUB_CLIENT_SECRET="" -CLIENT_URL="change-me" -API_SERVER_URL="change-me" +STRIPE_SECRET_KEY="" +STRIPE_WEBHOOK_SECRET="" + +CLIENT_URL="" +API_SERVER_URL="" # OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318" diff --git a/apps/server/src/libs/auth.ts b/apps/server/src/libs/auth.ts index 3284696fe..b71472aa7 100644 --- a/apps/server/src/libs/auth.ts +++ b/apps/server/src/libs/auth.ts @@ -296,6 +296,8 @@ export async function seedTrustedClients(db: Database, env: Env): Promise export function createAuth(db: Database, env: Env, metrics?: AuthMetrics | null) { return betterAuth({ + secret: env.BETTER_AUTH_SECRET, + database: drizzleAdapter(db, { provider: 'pg', schema: { diff --git a/apps/server/src/libs/env.test.ts b/apps/server/src/libs/env.test.ts index a04b3de48..edd641c60 100644 --- a/apps/server/src/libs/env.test.ts +++ b/apps/server/src/libs/env.test.ts @@ -7,6 +7,7 @@ describe('parseEnv', () => { const env = parseEnv({ DATABASE_URL: 'postgres://example', REDIS_URL: 'redis://example', + BETTER_AUTH_SECRET: 'test-secret-at-least-32-characters-long', AUTH_GOOGLE_CLIENT_ID: 'google-client', AUTH_GOOGLE_CLIENT_SECRET: 'google-secret', AUTH_GITHUB_CLIENT_ID: 'github-client', diff --git a/apps/server/src/libs/env.ts b/apps/server/src/libs/env.ts index 2d827154e..5749fbff0 100644 --- a/apps/server/src/libs/env.ts +++ b/apps/server/src/libs/env.ts @@ -43,6 +43,11 @@ const EnvSchema = object({ DATABASE_URL: pipe(string(), nonEmpty('DATABASE_URL is required')), REDIS_URL: pipe(string(), nonEmpty('REDIS_URL is required')), + // Required: signs session cookies and encrypts JWKS private keys in DB. + // Must be stable across deploys/instances, otherwise every redeploy invalidates + // all existing sessions and forces users to re-login. + BETTER_AUTH_SECRET: pipe(string(), nonEmpty('BETTER_AUTH_SECRET is required')), + AUTH_GOOGLE_CLIENT_ID: pipe(string(), nonEmpty('AUTH_GOOGLE_CLIENT_ID is required')), AUTH_GOOGLE_CLIENT_SECRET: pipe(string(), nonEmpty('AUTH_GOOGLE_CLIENT_SECRET is required')), AUTH_GITHUB_CLIENT_ID: pipe(string(), nonEmpty('AUTH_GITHUB_CLIENT_ID is required')),