Skip to content

Commit fa8a758

Browse files
authored
fix: cache serverAuth per-host instead of singleton (#125)
1 parent b89ecb6 commit fa8a758

File tree

8 files changed

+71
-7
lines changed

8 files changed

+71
-7
lines changed

src/runtime/server/utils/auth.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { withoutProtocol } from 'ufo'
1010

1111
type AuthInstance = Auth<ReturnType<typeof createServerAuth>>
1212

13-
let _auth: AuthInstance | null = null
13+
const _authCache = new Map<string, AuthInstance>()
1414
let _baseURLInferenceLogged = false
1515

1616
function normalizeLoopbackOrigin(origin: string): string {
@@ -144,24 +144,28 @@ function getBaseURL(event?: H3Event): string {
144144
throw new Error('siteUrl required. Set NUXT_PUBLIC_SITE_URL.')
145145
}
146146

147-
/** Returns Better Auth instance. Pass event for accurate URL detection on first call. */
147+
/** Returns Better Auth instance. Caches per resolved host (or single instance when siteUrl is explicit). */
148148
export function serverAuth(event?: H3Event): AuthInstance {
149-
if (_auth)
150-
return _auth
151-
152149
const runtimeConfig = useRuntimeConfig()
153150
const siteUrl = getBaseURL(event)
151+
const hasExplicitSiteUrl = runtimeConfig.public.siteUrl && typeof runtimeConfig.public.siteUrl === 'string'
152+
const cacheKey = hasExplicitSiteUrl ? '__explicit__' : siteUrl
153+
154+
const cached = _authCache.get(cacheKey)
155+
if (cached)
156+
return cached
154157

155158
const database = createDatabase()
156159
const userConfig = createServerAuth({ runtimeConfig, db })
157160

158-
_auth = betterAuth({
161+
const auth = betterAuth({
159162
...userConfig,
160163
...(database && { database }),
161164
secondaryStorage: createSecondaryStorage(),
162165
secret: runtimeConfig.betterAuthSecret,
163166
baseURL: siteUrl,
164167
})
165168

166-
return _auth
169+
_authCache.set(cacheKey, auth)
170+
return auth
167171
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
setups.@onmax/nuxt-better-auth="0.0.2-alpha.21"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<NuxtPage />
3+
</template>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { defineClientAuth } from '../../../../src/runtime/config'
2+
3+
export default defineClientAuth({})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default defineNuxtConfig({
2+
modules: ['../../../src/module'],
3+
runtimeConfig: {
4+
betterAuthSecret: 'test-secret-for-testing-only-32chars!',
5+
},
6+
})
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default defineEventHandler((event) => {
2+
const auth = serverAuth(event) as { options?: { baseURL?: string } }
3+
return { baseURL: auth.options?.baseURL }
4+
})
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { defineServerAuth } from '../../../../src/runtime/config'
2+
3+
export default defineServerAuth(({ runtimeConfig }) => ({
4+
appName: 'Base URL inference test app',
5+
socialProviders: {
6+
github: {
7+
clientId: runtimeConfig.github?.clientId || 'test',
8+
clientSecret: runtimeConfig.github?.clientSecret || 'test',
9+
},
10+
},
11+
}))
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { fileURLToPath } from 'node:url'
2+
import { setup, url } from '@nuxt/test-utils/e2e'
3+
import { describe, expect, it } from 'vitest'
4+
5+
describe('serverAuth baseURL inference', async () => {
6+
await setup({
7+
rootDir: fileURLToPath(new URL('./cases/base-url-inference', import.meta.url)),
8+
})
9+
10+
it('derives baseURL from each request host when siteUrl is unset', async () => {
11+
const firstResponse = await fetch(url('/api/test/base-url'), {
12+
headers: {
13+
'x-forwarded-host': 'first.example.com',
14+
'x-forwarded-proto': 'https',
15+
},
16+
})
17+
expect(firstResponse.status).toBe(200)
18+
const firstBody = await firstResponse.json() as { baseURL: string | undefined }
19+
20+
const secondResponse = await fetch(url('/api/test/base-url'), {
21+
headers: {
22+
'x-forwarded-host': 'second.example.com',
23+
'x-forwarded-proto': 'https',
24+
},
25+
})
26+
expect(secondResponse.status).toBe(200)
27+
const secondBody = await secondResponse.json() as { baseURL: string | undefined }
28+
29+
expect(firstBody.baseURL).toBe('https://first.example.com')
30+
expect(secondBody.baseURL).toBe('https://second.example.com')
31+
})
32+
})

0 commit comments

Comments
 (0)