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.
This commit is contained in:
RainbowBird
2026-04-13 18:13:19 +08:00
parent 61371060e7
commit fc0ad03411
4 changed files with 18 additions and 8 deletions
+10 -8
View File
@@ -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"
+2
View File
@@ -296,6 +296,8 @@ export async function seedTrustedClients(db: Database, env: Env): Promise<void>
export function createAuth(db: Database, env: Env, metrics?: AuthMetrics | null) {
return betterAuth({
secret: env.BETTER_AUTH_SECRET,
database: drizzleAdapter(db, {
provider: 'pg',
schema: {
+1
View File
@@ -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',
+5
View File
@@ -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')),