Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions docs/content/3.guides/9.production-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Checklist and best practices for deploying Nuxt Better Auth in prod

## Environment Variables

Production requires these environment variables:
Production requires these environment variables at runtime:

```ini [.env.production]
# Required: 32+ character secret for session encryption
Expand All @@ -25,7 +25,7 @@ GOOGLE_CLIENT_SECRET="..."
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```

The secret must be at least 32 characters. Shorter secrets will cause the module to throw an error.
The secret must be at least 32 characters. Shorter secrets will cause auth initialization to throw an error at runtime.

## Security Checklist

Expand Down Expand Up @@ -79,6 +79,10 @@ export default defineEventHandler((event) => {

For production, consider using Cloudflare rate limiting or a Redis-backed solution.

### Separate Build And Runtime Environments

Some platforms, including Cloudflare deployments, expose build-time and runtime environment variables separately. `NUXT_BETTER_AUTH_SECRET` must be available to the deployed runtime, but it does not need to be present in the build container.

### Trusted Origins for Preview Environments

If your workflow uses preview URLs (for example, `*.workers.dev`), include those origins in your Better Auth server config.
Expand Down Expand Up @@ -117,7 +121,11 @@ export default defineNuxtConfig({

### "NUXT_BETTER_AUTH_SECRET must be at least 32 characters"

Your secret is too short. Generate a new one using the command above. `BETTER_AUTH_SECRET` is still accepted as a fallback, but `NUXT_BETTER_AUTH_SECRET` is the recommended variable.
Your secret is too short. Generate a new one using the command above. This error is raised when auth initializes at runtime. `BETTER_AUTH_SECRET` is still accepted as a fallback, but `NUXT_BETTER_AUTH_SECRET` is the recommended variable.

### "NUXT_BETTER_AUTH_SECRET is required in production"

The deployed server runtime could not resolve an auth secret. Set `NUXT_BETTER_AUTH_SECRET` or `BETTER_AUTH_SECRET` in the runtime environment for your app.

### "siteUrl required in production"

Expand Down
8 changes: 0 additions & 8 deletions src/module/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,6 @@ export function setupRuntimeConfig(input: SetupRuntimeConfigInput): { useHubKV:
const currentSecret = nuxt.options.runtimeConfig.betterAuthSecret as string | undefined
nuxt.options.runtimeConfig.betterAuthSecret = currentSecret || process.env.NUXT_BETTER_AUTH_SECRET || process.env.BETTER_AUTH_SECRET || ''

const betterAuthSecret = nuxt.options.runtimeConfig.betterAuthSecret as string
if (!nuxt.options.dev && !nuxt.options._prepare && !betterAuthSecret) {
throw new Error('[nuxt-better-auth] NUXT_BETTER_AUTH_SECRET is required in production. Set NUXT_BETTER_AUTH_SECRET or BETTER_AUTH_SECRET environment variable.')
}
if (betterAuthSecret && betterAuthSecret.length < 32) {
throw new Error('[nuxt-better-auth] NUXT_BETTER_AUTH_SECRET must be at least 32 characters for security')
}

nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth as Record<string, unknown>, {
hubSecondaryStorage: options.hubSecondaryStorage ?? false,
}) as AuthPrivateRuntimeConfig
Expand Down
4 changes: 3 additions & 1 deletion src/runtime/server/utils/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getRequestHost, getRequestProtocol } from 'h3'
import { useRuntimeConfig } from 'nitropack/runtime'
import { withoutProtocol } from 'ufo'
import { resolveCustomSecondaryStorageRequirement } from './custom-secondary-storage'
import { validateAuthSecret } from './validate-secret'

type AuthOptions = ReturnType<typeof createServerAuth>
type AuthInstance = ReturnType<typeof betterAuth<AuthOptions>>
Expand Down Expand Up @@ -256,6 +257,7 @@ export function serverAuth(event?: H3Event): AuthInstance {
if (cached)
return cached

const betterAuthSecret = validateAuthSecret(runtimeConfig.betterAuthSecret)
const database = createDatabase()
const userConfig = createServerAuth({ runtimeConfig, db }) as BetterAuthOptions & {
secondaryStorage?: BetterAuthOptions['secondaryStorage']
Expand All @@ -275,7 +277,7 @@ export function serverAuth(event?: H3Event): AuthInstance {
...userConfig,
...(database && { database }),
...(hubSecondaryStorage === true && { secondaryStorage: createSecondaryStorage() }),
secret: runtimeConfig.betterAuthSecret,
secret: betterAuthSecret,
baseURL: siteUrl,
trustedOrigins,
})
Expand Down
8 changes: 8 additions & 0 deletions src/runtime/server/utils/validate-secret.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function validateAuthSecret(secret: string | undefined): string {
if (!import.meta.dev && !secret)
throw new Error('[nuxt-better-auth] NUXT_BETTER_AUTH_SECRET is required in production. Set NUXT_BETTER_AUTH_SECRET or BETTER_AUTH_SECRET environment variable.')
if (secret && secret.length < 32)
throw new Error('[nuxt-better-auth] NUXT_BETTER_AUTH_SECRET must be at least 32 characters for security')

return secret || ''
}
5 changes: 3 additions & 2 deletions test/runtime-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ describe('setupRuntimeConfig secret resolution', () => {
expect((nuxt.options as any).runtimeConfig.betterAuthSecret).toBe('fallback-secret-for-testing-only-32chars')
})

it('throws in production when no secret is configured', () => {
it('does not throw in production when no secret is configured', () => {
const nuxt = createNuxtWithRuntimeConfig()
nuxt.options.dev = false
const consola = createConsolaMock()
Expand All @@ -147,7 +147,8 @@ describe('setupRuntimeConfig secret resolution', () => {
databaseProvider: 'none',
hasNuxtHub: false,
consola,
})).toThrow('NUXT_BETTER_AUTH_SECRET is required in production')
})).not.toThrow()
expect((nuxt.options as any).runtimeConfig.betterAuthSecret).toBe('')
})
})

Expand Down
17 changes: 17 additions & 0 deletions test/server-auth-runtime-validation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest'
import { validateAuthSecret } from '../src/runtime/server/utils/validate-secret'

describe('validateAuthSecret', () => {
it('throws when secret is missing', () => {
expect(() => validateAuthSecret('')).toThrow('NUXT_BETTER_AUTH_SECRET is required in production')
})

it('throws when secret is shorter than 32 characters', () => {
expect(() => validateAuthSecret('too-short')).toThrow('NUXT_BETTER_AUTH_SECRET must be at least 32 characters')
})

it('returns valid secret', () => {
const secret = 'a'.repeat(32)
expect(validateAuthSecret(secret)).toBe(secret)
})
})
Loading