Skip to content

Commit 9c7aa2f

Browse files
committed
feat: add KV secondary storage and baseURL auto-detection
1 parent a8fe655 commit 9c7aa2f

File tree

5 files changed

+96
-5
lines changed

5 files changed

+96
-5
lines changed

docs/content/1.getting-started/2.configuration.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,33 @@ title: Server Configuration
33
description: Create `server/auth.config.ts` with your Better Auth setup.
44
---
55

6-
The module requires a Better Auth config file at `server/auth.config.ts`.
6+
## Module Options
7+
8+
Configure in `nuxt.config.ts`:
9+
10+
```ts
11+
export default defineNuxtConfig({
12+
auth: {
13+
redirects: { login: '/login', guest: '/' },
14+
secondaryStorage: true, // Enable KV for faster session lookups
15+
}
16+
})
17+
```
18+
19+
### secondaryStorage
20+
21+
When enabled, uses NuxtHub KV (`hub:kv`) as secondary storage for sessions.
22+
This reduces database queries by caching sessions in KV with TTL support.
23+
24+
**Requirements:**
25+
- `hub.kv: true` must be set in nuxt.config
26+
- If KV is not enabled, the module logs a warning and disables secondaryStorage
27+
28+
---
29+
30+
## Server Auth Config
31+
32+
The module requires a Better Auth config file at `server/auth.config.ts`.
733
It must export a default function created with `defineServerAuth`.
834

935
## Example

docs/content/2.core-concepts/0.how-it-works.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,17 @@ The module validates both files exist at build time.
1717
- `serverAuth()` creates and caches a Better Auth instance by calling your config and injecting:
1818
- NuxtHub database via the Drizzle adapter
1919
- `runtimeConfig.betterAuthSecret`
20-
- `runtimeConfig.public.siteUrl` as `baseURL`
20+
- `baseURL` (auto-detected, see below)
21+
- Optional KV secondary storage when `auth.secondaryStorage: true`
22+
23+
### BaseURL Detection
24+
25+
The module auto-detects the base URL from:
26+
1. `runtimeConfig.public.siteUrl` (recommended)
27+
2. Fallback: current request origin via `getRequestURL()`
28+
29+
Setting `NUXT_PUBLIC_SITE_URL` is optional but recommended for production.
30+
2131
- A catch-all API handler at `/api/auth/**` that forwards to Better Auth.
2232
- Nitro middleware for `/api/**` that enforces `routeRules.role` using `requireUserSession(...)`.
2333

src/module.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export type { BetterAuthModuleOptions } from './runtime/config'
1313

1414
export default defineNuxtModule<BetterAuthModuleOptions>({
1515
meta: { name: '@onmax/nuxt-better-auth', configKey: 'auth', compatibility: { nuxt: '>=3.0.0' } },
16-
defaults: { redirects: { login: '/login', guest: '/' } },
16+
defaults: { redirects: { login: '/login', guest: '/' }, secondaryStorage: false },
1717
async setup(options, nuxt) {
1818
const resolver = createResolver(import.meta.url)
1919

@@ -29,6 +29,16 @@ export default defineNuxtModule<BetterAuthModuleOptions>({
2929
if (!clientConfigExists)
3030
throw new Error('[@onmax/nuxt-better-auth] Missing app/auth.client.ts - export createAppAuthClient()')
3131

32+
// Validate KV is enabled if secondaryStorage requested
33+
let secondaryStorageEnabled = options.secondaryStorage ?? false
34+
if (secondaryStorageEnabled) {
35+
const hub = nuxt.options.hub as { kv?: boolean } | undefined
36+
if (!hub?.kv) {
37+
console.warn('[nuxt-better-auth] secondaryStorage requires hub.kv: true in nuxt.config.ts. Disabling.')
38+
secondaryStorageEnabled = false
39+
}
40+
}
41+
3242
// Expose module options to runtime config
3343
nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {}
3444
nuxt.options.runtimeConfig.public.auth = defu(nuxt.options.runtimeConfig.public.auth as Record<string, unknown>, {
@@ -38,6 +48,9 @@ export default defineNuxtModule<BetterAuthModuleOptions>({
3848
},
3949
})
4050

51+
// Private runtime config (server-only)
52+
nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth as Record<string, unknown>, { secondaryStorage: secondaryStorageEnabled })
53+
4154
// Register #nuxt-better-auth alias for type augmentation
4255
nuxt.options.alias['#nuxt-better-auth'] = resolver.resolve('./runtime/types/augment')
4356

src/runtime/config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,20 @@ export interface BetterAuthModuleOptions {
2222
login?: string // default: '/login'
2323
guest?: string // default: '/'
2424
}
25+
/** Enable KV secondary storage for sessions. Requires hub.kv: true */
26+
secondaryStorage?: boolean
2527
}
2628

2729
// Runtime config type for public.auth
2830
export interface AuthRuntimeConfig {
2931
redirects: { login: string, guest: string }
3032
}
3133

34+
// Private runtime config (server-only)
35+
export interface AuthPrivateRuntimeConfig {
36+
secondaryStorage: boolean
37+
}
38+
3239
export function defineServerAuth<T extends ServerAuthConfig>(config: (ctx: ServerAuthContext) => T): (ctx: ServerAuthContext) => T {
3340
return config
3441
}

src/runtime/server/utils/auth.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,61 @@
11
import createServerAuth from '#auth/server'
2-
import { useRuntimeConfig } from '#imports'
2+
import { useEvent, useRuntimeConfig } from '#imports'
33
import { betterAuth } from 'better-auth'
44
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
5+
import { getRequestURL } from 'h3'
56
import { db, schema } from 'hub:db'
67

78
type AuthInstance = ReturnType<typeof betterAuth>
89
let _auth: AuthInstance | undefined
910

11+
function getBaseURL(siteUrl?: string): string {
12+
if (siteUrl)
13+
return siteUrl
14+
15+
// Fallback: detect from current request
16+
try {
17+
const event = useEvent()
18+
return getRequestURL(event).origin
19+
}
20+
catch {
21+
return ''
22+
}
23+
}
24+
25+
let _kv: typeof import('hub:kv').kv | undefined
26+
27+
async function getKV() {
28+
if (!_kv) {
29+
const { kv } = await import('hub:kv')
30+
_kv = kv
31+
}
32+
return _kv
33+
}
34+
35+
function createSecondaryStorage() {
36+
return {
37+
get: async (key: string) => (await getKV()).get(`_auth:${key}`),
38+
set: async (key: string, value: unknown, ttl?: number) => (await getKV()).set(`_auth:${key}`, value, { ttl }),
39+
delete: async (key: string) => (await getKV()).del(`_auth:${key}`),
40+
}
41+
}
42+
1043
export function serverAuth(): AuthInstance {
1144
if (_auth)
1245
return _auth
1346

1447
const runtimeConfig = useRuntimeConfig()
48+
const authConfig = runtimeConfig.auth as { secondaryStorage?: boolean } | undefined
1549

1650
// User's config function receives context with db
1751
const userConfig = createServerAuth({ runtimeConfig, db })
1852

1953
_auth = betterAuth({
2054
...userConfig,
2155
database: drizzleAdapter(db, { provider: 'sqlite', schema }),
56+
secondaryStorage: authConfig?.secondaryStorage ? createSecondaryStorage() : undefined,
2257
secret: runtimeConfig.betterAuthSecret,
23-
baseURL: runtimeConfig.public.siteUrl,
58+
baseURL: getBaseURL(runtimeConfig.public.siteUrl as string | undefined),
2459
})
2560

2661
return _auth

0 commit comments

Comments
 (0)