@nuxtjs/better-auth 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +104 -44
  3. package/dist/runtime/app/composables/useAuthRequestFetch.js +25 -9
  4. package/dist/runtime/app/composables/useUserSession.js +3 -1
  5. package/dist/runtime/app/middleware/auth.global.js +9 -10
  6. package/dist/runtime/server/api/_better-auth/accounts.get.js +8 -1
  7. package/dist/runtime/server/api/auth/[...all].js +2 -0
  8. package/dist/runtime/server/internal/app-secret-compat.d.ts +1 -0
  9. package/dist/runtime/server/internal/app-secret-compat.js +1 -0
  10. package/dist/runtime/server/internal/app-secret-unavailable.d.ts +2 -0
  11. package/dist/runtime/server/internal/app-secret-unavailable.js +1 -0
  12. package/dist/runtime/server/internal/app-secret.d.ts +4 -0
  13. package/dist/runtime/server/internal/app-secret.js +3 -0
  14. package/dist/runtime/server/internal/auth-secret.d.ts +25 -0
  15. package/dist/runtime/server/internal/auth-secret.js +40 -0
  16. package/dist/runtime/server/internal/cookie-headers.d.ts +1 -0
  17. package/dist/runtime/server/internal/cookie-headers.js +1 -1
  18. package/dist/runtime/server/internal/{nitro3.d.ts → portable.d.ts} +3 -4
  19. package/dist/runtime/server/internal/{nitro3.js → portable.js} +8 -8
  20. package/dist/runtime/server/middleware/route-access.js +2 -0
  21. package/dist/runtime/server/plugins/auth-secret.d.ts +7 -0
  22. package/dist/runtime/server/plugins/auth-secret.js +4 -0
  23. package/dist/runtime/server/tsconfig.json +1 -0
  24. package/dist/runtime/server/utils/auth.d.ts +6 -0
  25. package/dist/runtime/server/utils/auth.js +51 -29
  26. package/dist/runtime/server/utils/session.js +40 -2
  27. package/dist/runtime/test-utils/e2e.js +4 -2
  28. package/dist/runtime/test-utils/server/handler.js +1 -1
  29. package/package.json +13 -13
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxtjs/better-auth",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "configKey": "auth",
5
5
  "compatibility": {
6
6
  "nuxt": ">=4.0.0"
package/dist/module.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { mkdir, writeFile } from 'node:fs/promises';
3
- import { getLayerDirectories, updateTemplates, addServerImports, addServerHandler, addImports, addPlugin, addComponentsDir, addRouteMiddleware, extendPages, addTemplate, hasNuxtModule, addTypeTemplate, defineNuxtModule, createResolver } from '@nuxt/kit';
3
+ import { getLayerDirectories, resolveServerVariant, hasNuxtCompatibility, updateTemplates, addServerImports, addServerHandler, addNitroPlugin, addImports, addPlugin, addComponentsDir, addRouteMiddleware, extendPages, addTemplate, hasNuxtModule, addTypeTemplate, defineNuxtModule, createResolver, getNitroVersion } from '@nuxt/kit';
4
4
  import { consola as consola$1 } from 'consola';
5
5
  import { defineDiagnostics, Diagnostic, formatDiagnostic } from 'nostics';
6
6
  import { isAbsolute, join, resolve, relative, dirname } from 'pathe';
@@ -13,7 +13,7 @@ import { isCI, isTest } from 'std-env';
13
13
  import { isInternalAssetPath } from '../dist/runtime/internal/auth-route-rules.js';
14
14
  export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
15
15
 
16
- const version = "0.3.6";
16
+ const version = "0.3.8";
17
17
 
18
18
  const CONFIG_EXTENSIONS = [".ts", ".js", ".mts", ".cts", ".mjs", ".cjs"];
19
19
  const CONFIG_EXTENSION_RE = /\.[cm]?[jt]s$/;
@@ -198,13 +198,26 @@ function resolveNitro3RouteRulesTarget(nuxtRootDir) {
198
198
  return fallback;
199
199
  }
200
200
  }
201
- function resolveNitroCompatibilityImports(nuxtVersion) {
202
- const major = Number.parseInt(nuxtVersion, 10);
203
- const nitroV3 = Number.isFinite(major) && major >= 5;
204
- return {
205
- h3: nitroV3 ? "nitro/h3" : "h3",
206
- runtime: nitroV3 ? "nitro3" : "nitro2"
207
- };
201
+ function resolveServerRuntimeShim(resolve) {
202
+ const nitro2 = resolve("./runtime/server/internal/nitro2");
203
+ return resolveServerVariant({
204
+ nuxt: resolve("./runtime/server/internal/portable"),
205
+ nitro2
206
+ }) ?? nitro2;
207
+ }
208
+ function resolveH3TypesPath(nitroMajor) {
209
+ return nitroMajor === 3 ? "nitro/h3" : "h3";
210
+ }
211
+ async function resolveAppSecretShim(resolve) {
212
+ const canDeriveAppSecret = await hasNuxtCompatibility({ nuxt: ">=4.6.0-0" });
213
+ return resolve(`./runtime/server/internal/${canDeriveAppSecret ? "app-secret" : "app-secret-unavailable"}`);
214
+ }
215
+ async function resolveAuthSecretPlugin(resolve) {
216
+ if (!await hasNuxtCompatibility({ nuxt: ">=4.6.0-0" }))
217
+ return;
218
+ if (!resolveServerVariant({ nitro2: true, nitro3: true, nuxt: false }))
219
+ return;
220
+ return resolve("./runtime/server/plugins/auth-secret");
208
221
  }
209
222
 
210
223
  function configFilename(file) {
@@ -286,15 +299,18 @@ function registerTemplateHmrHook(nuxt) {
286
299
  });
287
300
  }
288
301
  function registerServerRuntime(input) {
289
- const { clientOnly, resolve } = input;
302
+ const { authSecretPlugin, clientOnly, resolve } = input;
290
303
  if (!clientOnly) {
291
304
  addServerImports([
292
305
  { name: "defineServerAuth", from: resolve("./runtime/config") },
293
306
  { name: "serverAuth", from: resolve("./runtime/server/utils/auth") },
307
+ { name: "ensureServerAuth", from: resolve("./runtime/server/utils/auth") },
294
308
  ...["getRequestSession", "getUserSession", "setRequestSession", "refreshSessionCookieCache", "setSessionCookie", "createSession", "requireUserSession"].map((name) => ({ name, from: resolve("./runtime/server/utils/session") }))
295
309
  ]);
296
310
  addServerHandler({ middleware: true, handler: resolve("./runtime/server/middleware/route-access") });
297
311
  addServerHandler({ route: "/api/auth/**", handler: resolve("./runtime/server/api/auth/[...all]") });
312
+ if (authSecretPlugin)
313
+ addNitroPlugin({ nitro2: authSecretPlugin, nitro3: authSecretPlugin });
298
314
  }
299
315
  addImports([
300
316
  "runWithSessionRefresh",
@@ -607,6 +623,18 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
607
623
  const hubCasing = getHubCasing(hub);
608
624
  const schemaOptions = { ...options.schema, useUuid: userConfig.advanced?.database?.generateId === "uuid", casing: options.schema?.casing ?? hubCasing };
609
625
  const schemaCode = await generateDrizzleSchema(authOptions, dialect, schemaOptions);
626
+ const { getAuthTables } = await import('better-auth/db');
627
+ const { initGetModelName } = await import('better-auth/adapters');
628
+ const authTables = getAuthTables(authOptions);
629
+ const getModelName = initGetModelName({
630
+ schema: authTables,
631
+ usePlural: schemaOptions.usePlural ?? false
632
+ });
633
+ const modelNames = {};
634
+ for (const name of ["user", "session", "account", "verification"]) {
635
+ if (authTables[name])
636
+ modelNames[name] = getModelName(name);
637
+ }
610
638
  const schemaDir = join(nuxt.options.buildDir, "better-auth");
611
639
  const schemaPathTs = join(schemaDir, `schema.${dialect}.ts`);
612
640
  const schemaPathMjs = join(schemaDir, `schema.${dialect}.mjs`);
@@ -622,6 +650,7 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
622
650
  addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
623
651
  addTemplate({ filename: `better-auth/schema.${dialect}.mjs`, getContents: () => schemaCode, write: true });
624
652
  consola.info(`Generated ${dialect} schema (.ts + .mjs)`);
653
+ return modelNames;
625
654
  } catch (error) {
626
655
  throw error instanceof Diagnostic ? error : diagnostics.NUXT_AUTH_SCHEMA_GENERATION_FAILED({ cause: error });
627
656
  }
@@ -630,45 +659,62 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
630
659
  const DEFAULT_SECRET_ENV = "NUXT_BETTER_AUTH_SECRET";
631
660
  const FALLBACK_SECRET_ENV = "BETTER_AUTH_SECRET";
632
661
  const VERSIONED_SECRET_ENV = "BETTER_AUTH_SECRETS";
662
+ const APP_SECRET_ENV = "NUXT_APP_SECRET";
633
663
  const generateSecret = () => randomBytes(32).toString("hex");
634
664
  function readEnvFile(rootDir) {
635
665
  const envPath = join(rootDir, ".env");
636
666
  return existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
637
667
  }
638
- function hasEnvSecret(rootDir) {
668
+ function hasEnvSecret(rootDir, names) {
639
669
  const envFile = readEnvFile(rootDir);
640
- return [DEFAULT_SECRET_ENV, FALLBACK_SECRET_ENV, VERSIONED_SECRET_ENV].some((name) => {
670
+ return names.some((name) => {
641
671
  const match = envFile.match(new RegExp(`^${name}=(.+)$`, "m"));
642
672
  return !!match && !!match[1] && match[1].trim().length > 0;
643
673
  });
644
674
  }
645
- function appendSecretToEnv(rootDir, secret) {
675
+ function appendSecretToEnv(rootDir, name, secret) {
646
676
  const envPath = join(rootDir, ".env");
647
677
  let content = readEnvFile(rootDir);
648
678
  if (content.length > 0 && !content.endsWith("\n"))
649
679
  content += "\n";
650
- content += `${DEFAULT_SECRET_ENV}=${secret}
680
+ content += `${name}=${secret}
651
681
  `;
652
682
  writeFileSync(envPath, content, "utf-8");
653
683
  }
684
+ function getSecretEnvName(appSecret) {
685
+ return appSecret ? APP_SECRET_ENV : DEFAULT_SECRET_ENV;
686
+ }
687
+ function applyPromptedAppSecret(secret) {
688
+ process.env[APP_SECRET_ENV] = secret;
689
+ delete process.env.NUXT_APP_SECRET_GENERATED;
690
+ }
654
691
  async function promptForSecret(rootDir, consola, options = {}) {
655
692
  const configuredSecret = options.configuredSecret?.trim();
656
693
  if (configuredSecret)
657
694
  return void 0;
658
- if (process.env.NUXT_BETTER_AUTH_SECRET || process.env.BETTER_AUTH_SECRET || process.env.BETTER_AUTH_SECRETS || hasEnvSecret(rootDir))
695
+ if (process.env.NUXT_BETTER_AUTH_SECRET || process.env.BETTER_AUTH_SECRET || process.env.BETTER_AUTH_SECRETS)
696
+ return void 0;
697
+ const secretEnvNames = [DEFAULT_SECRET_ENV, FALLBACK_SECRET_ENV, VERSIONED_SECRET_ENV];
698
+ if (options.appSecret)
699
+ secretEnvNames.push(APP_SECRET_ENV);
700
+ if (hasEnvSecret(rootDir, secretEnvNames))
701
+ return void 0;
702
+ const generatedAppSecret = process.env.NUXT_APP_SECRET_GENERATED === "1";
703
+ if (options.appSecret && !generatedAppSecret && (process.env.NUXT_APP_SECRET || options.configuredAppSecret?.trim()))
659
704
  return void 0;
660
705
  const hasTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
661
706
  if (options.prepare || !hasTty) {
662
- consola.warn("[nuxt-better-auth] Skipping auth secret prompt (non-interactive). Set NUXT_BETTER_AUTH_SECRET, BETTER_AUTH_SECRET, or BETTER_AUTH_SECRETS.");
707
+ consola.warn(options.appSecret ? "[nuxt-better-auth] Skipping auth secret prompt (non-interactive). Set NUXT_APP_SECRET, or NUXT_BETTER_AUTH_SECRET, BETTER_AUTH_SECRET, or BETTER_AUTH_SECRETS." : "[nuxt-better-auth] Skipping auth secret prompt (non-interactive). Set NUXT_BETTER_AUTH_SECRET, BETTER_AUTH_SECRET, or BETTER_AUTH_SECRETS.");
663
708
  return void 0;
664
709
  }
710
+ const envName = getSecretEnvName(options.appSecret);
665
711
  if (isCI || isTest) {
666
712
  const secret2 = generateSecret();
667
- appendSecretToEnv(rootDir, secret2);
668
- consola.info("Generated NUXT_BETTER_AUTH_SECRET and added to .env (CI/test mode)");
713
+ appendSecretToEnv(rootDir, envName, secret2);
714
+ consola.info(`Generated ${envName} and added to .env (CI/test mode)`);
669
715
  return secret2;
670
716
  }
671
- consola.box("An auth secret is required for authentication.\nThis will add NUXT_BETTER_AUTH_SECRET to your .env file.\nBETTER_AUTH_SECRET and BETTER_AUTH_SECRETS are also supported.");
717
+ consola.box(options.appSecret ? "An auth secret is required for authentication.\nThis will add NUXT_APP_SECRET to your .env file. The auth secret is derived from it.\nNUXT_BETTER_AUTH_SECRET, BETTER_AUTH_SECRET and BETTER_AUTH_SECRETS are also supported." : "An auth secret is required for authentication.\nThis will add NUXT_BETTER_AUTH_SECRET to your .env file.\nBETTER_AUTH_SECRET and BETTER_AUTH_SECRETS are also supported.");
672
718
  const choice = await consola.prompt("How do you want to set it?", {
673
719
  type: "select",
674
720
  options: [
@@ -695,14 +741,14 @@ async function promptForSecret(rootDir, consola, options = {}) {
695
741
  }
696
742
  const preview = `${secret.slice(0, 8)}...${secret.slice(-4)}`;
697
743
  const confirm = await consola.prompt(`Add to .env:
698
- ${DEFAULT_SECRET_ENV}=${preview}
744
+ ${envName}=${preview}
699
745
  Proceed?`, { type: "confirm", initial: true, cancel: "null" });
700
746
  if (typeof confirm === "symbol" || !confirm) {
701
747
  consola.info("Cancelled. Secret not written.");
702
748
  return void 0;
703
749
  }
704
- appendSecretToEnv(rootDir, secret);
705
- consola.success("Added NUXT_BETTER_AUTH_SECRET to .env");
750
+ appendSecretToEnv(rootDir, envName, secret);
751
+ consola.success(`Added ${envName} to .env`);
706
752
  return secret;
707
753
  }
708
754
 
@@ -755,7 +801,7 @@ function setupRuntimeConfig(input) {
755
801
  if (clientOnly) {
756
802
  const siteUrl = nuxt.options.runtimeConfig.public.siteUrl;
757
803
  if (!siteUrl)
758
- consola.warn("clientOnly mode: set runtimeConfig.public.siteUrl (or NUXT_PUBLIC_SITE_URL) to your frontend URL");
804
+ consola.warn("clientOnly mode: set runtimeConfig.public.siteUrl (or NUXT_PUBLIC_SITE_URL) to your auth backend URL, or set baseURL in defineClientAuth()");
759
805
  consola.info("clientOnly mode enabled - server utilities (serverAuth, getRequestSession, getUserSession, requireUserSession) are not available");
760
806
  return { secondaryStorageEnabled };
761
807
  }
@@ -914,7 +960,7 @@ export { db }`;
914
960
  return `export function createDatabase() { return undefined }
915
961
  export const db = undefined`;
916
962
  }
917
- function buildSchemaExportCode(hasHubDb, hubDialect) {
963
+ function buildSchemaExportCode(hasHubDb, hubDialect, modelNames = {}) {
918
964
  if (!hasHubDb) {
919
965
  return `export const user = undefined
920
966
  export const session = undefined
@@ -927,14 +973,14 @@ export const schema = undefined
927
973
  import * as generatedSchema from './schema.${hubDialect}.mjs'
928
974
 
929
975
  const getGeneratedTable = name => generatedSchema[name] ?? generatedSchema[\`\${name}s\`]
930
- export const user = getGeneratedTable('user')
931
- export const session = getGeneratedTable('session')
932
- export const account = getGeneratedTable('account')
933
- export const verification = getGeneratedTable('verification')
976
+ export const user = getGeneratedTable(${JSON.stringify(modelNames.user ?? "user")})
977
+ export const session = getGeneratedTable(${JSON.stringify(modelNames.session ?? "session")})
978
+ export const account = getGeneratedTable(${JSON.stringify(modelNames.account ?? "account")})
979
+ export const verification = getGeneratedTable(${JSON.stringify(modelNames.verification ?? "verification")})
934
980
  export const schema = { ...generatedSchema, user, session, account, verification }
935
981
  `;
936
982
  }
937
- function buildSchemaExportTypes(hasHubDb, hubDialect) {
983
+ function buildSchemaExportTypes(hasHubDb, hubDialect, modelNames = {}) {
938
984
  if (!hasHubDb) {
939
985
  return `export declare const user: undefined
940
986
  export declare const session: undefined
@@ -952,10 +998,10 @@ type GeneratedTable<Name extends string> = Name extends keyof GeneratedSchema
952
998
  ? GeneratedSchema[\`\${Name}s\`]
953
999
  : undefined
954
1000
 
955
- export declare const user: GeneratedTable<'user'>
956
- export declare const session: GeneratedTable<'session'>
957
- export declare const account: GeneratedTable<'account'>
958
- export declare const verification: GeneratedTable<'verification'>
1001
+ export declare const user: GeneratedTable<${JSON.stringify(modelNames.user ?? "user")}>
1002
+ export declare const session: GeneratedTable<${JSON.stringify(modelNames.session ?? "session")}>
1003
+ export declare const account: GeneratedTable<${JSON.stringify(modelNames.account ?? "account")}>
1004
+ export declare const verification: GeneratedTable<${JSON.stringify(modelNames.verification ?? "verification")}>
959
1005
  export declare const schema: GeneratedSchema & {
960
1006
  user: typeof user
961
1007
  session: typeof session
@@ -1632,16 +1678,25 @@ const module$1 = defineNuxtModule({
1632
1678
  },
1633
1679
  async onInstall(nuxt) {
1634
1680
  const configuredSecret = nuxt.options.runtimeConfig?.betterAuthSecret;
1635
- const generatedSecret = await promptForSecret(nuxt.options.rootDir, consola, { configuredSecret, prepare: Boolean(nuxt.options._prepare) });
1636
- if (generatedSecret)
1681
+ const appSecret = await hasNuxtCompatibility({ nuxt: ">=4.6.0-0" }, nuxt);
1682
+ const generatedSecret = await promptForSecret(nuxt.options.rootDir, consola, {
1683
+ configuredSecret,
1684
+ prepare: Boolean(nuxt.options._prepare),
1685
+ appSecret,
1686
+ configuredAppSecret: nuxt.options.runtimeConfig?.appSecret
1687
+ });
1688
+ if (generatedSecret && appSecret)
1689
+ applyPromptedAppSecret(generatedSecret);
1690
+ else if (generatedSecret)
1637
1691
  process.env.NUXT_BETTER_AUTH_SECRET = generatedSecret;
1638
1692
  await createDefaultAuthConfigFiles(nuxt);
1639
1693
  },
1640
1694
  setup(options, nuxt) {
1641
1695
  const finishSetup = async () => {
1642
1696
  const resolver = createResolver(import.meta.url);
1643
- const nitroImports = resolveNitroCompatibilityImports(nuxt._version);
1644
- nuxt.options.alias["#better-auth/nitro-compat"] = resolver.resolve(`./runtime/server/internal/${nitroImports.runtime}`);
1697
+ const h3TypesPath = resolveH3TypesPath(getNitroVersion(nuxt));
1698
+ nuxt.options.alias["#better-auth/nitro-compat"] = resolveServerRuntimeShim(resolver.resolve);
1699
+ nuxt.options.alias["#better-auth/app-secret"] = await resolveAppSecretShim(resolver.resolve);
1645
1700
  const registeredPluginSources = {};
1646
1701
  await nuxt.callHook("better-auth:plugins:extend", registeredPluginSources);
1647
1702
  for (const source of [...registeredPluginSources.server || [], ...registeredPluginSources.client || []]) {
@@ -1657,6 +1712,7 @@ const module$1 = defineNuxtModule({
1657
1712
  });
1658
1713
  let serverConfigPath = setup.aliases["#auth/server"];
1659
1714
  let clientConfigPath = setup.aliases["#auth/client"];
1715
+ let schemaModelNames;
1660
1716
  if (serverConfigPath && setup.pluginSources.server.length) {
1661
1717
  const serverConfigCode = buildExtendedServerAuthCode(setup.configs.server.path, setup.pluginSources.server);
1662
1718
  serverConfigPath = addTemplate({
@@ -1686,13 +1742,13 @@ const module$1 = defineNuxtModule({
1686
1742
  nuxt.options.alias["#auth/database"] = databaseTemplate.dst;
1687
1743
  const schemaTemplate = addTemplate({
1688
1744
  filename: "better-auth/schema.mjs",
1689
- getContents: () => buildSchemaExportCode(setup.database.hasHubDb, setup.database.buildContext?.hubDialect ?? "sqlite"),
1745
+ getContents: () => buildSchemaExportCode(setup.database.hasHubDb, setup.database.buildContext?.hubDialect ?? "sqlite", schemaModelNames),
1690
1746
  write: true
1691
1747
  });
1692
1748
  nuxt.options.alias["#auth/schema"] = schemaTemplate.dst;
1693
1749
  addTemplate({
1694
1750
  filename: "better-auth/schema.d.ts",
1695
- getContents: () => buildSchemaExportTypes(setup.database.hasHubDb, setup.database.buildContext?.hubDialect ?? "sqlite"),
1751
+ getContents: () => buildSchemaExportTypes(setup.database.hasHubDb, setup.database.buildContext?.hubDialect ?? "sqlite", schemaModelNames),
1696
1752
  write: true
1697
1753
  });
1698
1754
  if (setup.schemaGeneration)
@@ -1726,14 +1782,14 @@ const module$1 = defineNuxtModule({
1726
1782
  hasHubDb: setup.serverTypes.hasHubDb,
1727
1783
  runtimeTypesPath: resolver.resolve("./runtime/types"),
1728
1784
  sharedServerConfigSafe: [setup.configs.server.path, ...setup.pluginSources.server].every(isServerConfigSharedTypeSafe),
1729
- h3TypesPath: nitroImports.h3,
1730
- nitro3RouteRulesTarget: nitroImports.runtime === "nitro3" ? resolveNitro3RouteRulesTarget(nuxt.options.rootDir) : void 0
1785
+ h3TypesPath,
1786
+ nitro3RouteRulesTarget: h3TypesPath === "nitro/h3" ? resolveNitro3RouteRulesTarget(nuxt.options.rootDir) : void 0
1731
1787
  });
1732
1788
  }
1733
1789
  if (setup.schemaGeneration) {
1734
1790
  if (setup.schemaGeneration.externalizeNuxtHubDatabase)
1735
1791
  registerNuxtHubDatabaseExternalHook(nuxt);
1736
- await setupBetterAuthSchema(
1792
+ schemaModelNames = await setupBetterAuthSchema(
1737
1793
  nuxt,
1738
1794
  serverConfigPath,
1739
1795
  options,
@@ -1745,11 +1801,15 @@ const module$1 = defineNuxtModule({
1745
1801
  runtimeTypesAugmentPath: setup.sharedTypes.runtimeTypesAugmentPath,
1746
1802
  runtimeTypesPath: resolver.resolve("./runtime/types"),
1747
1803
  clientConfigPath,
1748
- h3TypesPath: nitroImports.h3,
1804
+ h3TypesPath,
1749
1805
  clientOnly: setup.clientOnly
1750
1806
  });
1751
1807
  registerTemplateHmrHook(nuxt);
1752
- registerServerRuntime({ clientOnly: setup.clientOnly, resolve: resolver.resolve });
1808
+ registerServerRuntime({
1809
+ clientOnly: setup.clientOnly,
1810
+ resolve: resolver.resolve,
1811
+ authSecretPlugin: setup.clientOnly ? void 0 : await resolveAuthSecretPlugin(resolver.resolve)
1812
+ });
1753
1813
  registerAuthMiddleware(resolver.resolve);
1754
1814
  await registerDevtools({ nuxt, clientOnly: setup.clientOnly, hasHubDb: setup.database.hasHubDb, resolve: resolver.resolve });
1755
1815
  registerRouteRulesMetaHook(nuxt);
@@ -11,14 +11,30 @@ export function useAuthRequestFetch() {
11
11
  const clientOptions = createAppAuthClient.resolveOptions(siteUrl);
12
12
  const configuredBaseURL = clientOptions.baseURL ?? siteUrl;
13
13
  const baseURL = configuredBaseURL && new URL(configuredBaseURL).pathname.replace(/\/+$/, "") ? configuredBaseURL : joinURL(configuredBaseURL || "/", clientOptions.basePath ?? "/api/auth");
14
- const externalRequestFetch = requestFetch;
15
- return ((request, opts) => {
16
- if (typeof request !== "string" || !/^\/api\/auth(?=\/|$)/.test(request))
17
- return externalRequestFetch(request, opts);
18
- return externalRequestFetch(request.slice("/api/auth".length), {
19
- ...opts,
20
- baseURL,
21
- credentials: "include"
14
+ function wrapRequestFetch(fetch) {
15
+ const methods = /* @__PURE__ */ new Map();
16
+ return new Proxy(fetch, {
17
+ apply(target, thisArg, args) {
18
+ const [request, opts] = args;
19
+ if (typeof request !== "string" || !/^\/api\/auth(?=\/|$)/.test(request))
20
+ return Reflect.apply(target, thisArg, args);
21
+ return Reflect.apply(target, thisArg, [request.slice("/api/auth".length), {
22
+ ...opts,
23
+ baseURL,
24
+ credentials: "include"
25
+ }]);
26
+ },
27
+ get(target, prop, receiver) {
28
+ if (methods.has(prop))
29
+ return methods.get(prop);
30
+ const value = Reflect.get(target, prop, receiver);
31
+ if (typeof value !== "function" || prop !== "raw" && prop !== "create")
32
+ return value;
33
+ const method = prop === "raw" ? wrapRequestFetch(value) : (...args) => wrapRequestFetch(Reflect.apply(value, target, args));
34
+ methods.set(prop, method);
35
+ return method;
36
+ }
22
37
  });
23
- });
38
+ }
39
+ return wrapRequestFetch(requestFetch);
24
40
  }
@@ -74,6 +74,7 @@ export function useUserSession() {
74
74
  return;
75
75
  const previousUser = user.value;
76
76
  user.value = { ...user.value, ...updates };
77
+ const optimisticUser = user.value;
77
78
  if (!rawClient)
78
79
  return;
79
80
  try {
@@ -86,7 +87,8 @@ export function useUserSession() {
86
87
  throw new Error(normalizedError.message);
87
88
  }
88
89
  } catch (error) {
89
- user.value = previousUser;
90
+ if (user.value === optimisticUser)
91
+ user.value = previousUser;
90
92
  if (!(error instanceof Error)) {
91
93
  const normalizedError = normalizeAuthActionError(error);
92
94
  throw new Error(normalizedError.message);
@@ -1,5 +1,6 @@
1
1
  import { defu } from "defu";
2
2
  import { createRouter, toRouteMatcher } from "radix3";
3
+ import { parsePath, parseQuery } from "ufo";
3
4
  import { createError, defineNuxtRouteMiddleware, getRouteRules, navigateTo, useNuxtApp, useRequestHeaders, useRuntimeConfig } from "#imports";
4
5
  import { shouldSkipAuthRouteRules } from "../../internal/auth-route-rules.js";
5
6
  import { matchesUser } from "../../utils/match-user.js";
@@ -64,11 +65,11 @@ function resolveLoginRedirect(input) {
64
65
  return { to: loginTarget, external: false };
65
66
  if (!loginTarget.startsWith("/") || loginTarget.startsWith("//"))
66
67
  return { to: loginTarget, external: false };
67
- const [beforeHash, hash = ""] = loginTarget.split("#", 2);
68
- const [path, query = ""] = beforeHash.split("?", 2);
68
+ const { pathname: path, search, hash } = parsePath(loginTarget);
69
+ const query = search.slice(1);
69
70
  try {
70
- const params2 = new URLSearchParams(query);
71
- if (params2.has(redirectQueryKey))
71
+ const params = new URLSearchParams(query);
72
+ if (params.has(redirectQueryKey))
72
73
  return { to: loginTarget, external: false };
73
74
  } catch {
74
75
  return { to: loginTarget, external: false };
@@ -76,15 +77,13 @@ function resolveLoginRedirect(input) {
76
77
  if (import.meta.server) {
77
78
  const separator = query ? "&" : "";
78
79
  const encodedRedirect = encodeURIComponent(route.fullPath);
79
- const url = `${path}?${query}${separator}${redirectQueryKey}=${encodedRedirect}${hash ? `#${hash}` : ""}`;
80
+ const encodedKey = encodeURIComponent(redirectQueryKey);
81
+ const url = `${path}?${query}${separator}${encodedKey}=${encodedRedirect}${hash}`;
80
82
  return { to: url, external: true };
81
83
  }
82
- const params = new URLSearchParams(query);
83
- const queryObj = {};
84
- for (const [k, v] of params.entries())
85
- queryObj[k] = v;
84
+ const queryObj = parseQuery(query);
86
85
  queryObj[redirectQueryKey] = route.fullPath;
87
- return { to: { path, query: queryObj, ...hash ? { hash: `#${hash}` } : {} }, external: false };
86
+ return { to: { path, query: queryObj, ...hash ? { hash } : {} }, external: false };
88
87
  }
89
88
  async function getAuthRouteRules() {
90
89
  if (!authRouteRulesPromise) {
@@ -21,7 +21,14 @@ export default defineEventHandler(async (event) => {
21
21
  dbQuery.orderBy(desc(schema.account.createdAt)).limit(limit).offset(offset),
22
22
  countQuery
23
23
  ]);
24
- return { accounts, total: totalResult[0]?.count ?? 0, page, limit };
24
+ const safeAccounts = accounts.map((account) => ({
25
+ id: account.id,
26
+ userId: account.userId,
27
+ providerId: account.providerId,
28
+ accountId: account.accountId,
29
+ createdAt: account.createdAt
30
+ }));
31
+ return { accounts: safeAccounts, total: totalResult[0]?.count ?? 0, page, limit };
25
32
  } catch (error) {
26
33
  console.error("[DevTools] Fetch accounts failed:", error);
27
34
  return { accounts: [], total: 0, error: "Failed to fetch accounts" };
@@ -1,6 +1,8 @@
1
+ import { prepareAuthSecret } from "../../internal/auth-secret.js";
1
2
  import { defineEventHandler, toWebRequest } from "../../internal/nitro-compat.js";
2
3
  import { serverAuth } from "../../utils/auth.js";
3
4
  export default defineEventHandler(async (event) => {
5
+ await prepareAuthSecret();
4
6
  const auth = serverAuth(event);
5
7
  return auth.handler(toWebRequest(event));
6
8
  });
@@ -0,0 +1 @@
1
+ export * from '#better-auth/app-secret';
@@ -0,0 +1 @@
1
+ export * from "#better-auth/app-secret";
@@ -0,0 +1,2 @@
1
+ /** Nuxt < 4.6 has no `appSecret` to derive the Better Auth secret from. */
2
+ export declare const deriveAuthSecret: (() => Promise<string>) | undefined;
@@ -0,0 +1 @@
1
+ export const deriveAuthSecret = void 0;
@@ -0,0 +1,4 @@
1
+ /** The `deriveSecret()` purpose for the Better Auth secret. Changing it rotates every derived secret. */
2
+ export declare const AUTH_SECRET_PURPOSE = "better-auth:secret";
3
+ /** Derives the Better Auth secret from Nuxt's `appSecret` (`NUXT_APP_SECRET`), on Nuxt 4.6+. */
4
+ export declare const deriveAuthSecret: (() => Promise<string>) | undefined;
@@ -0,0 +1,3 @@
1
+ import { deriveSecret } from "nuxt/server";
2
+ export const AUTH_SECRET_PURPOSE = "better-auth:secret";
3
+ export const deriveAuthSecret = () => deriveSecret(AUTH_SECRET_PURPOSE);
@@ -0,0 +1,25 @@
1
+ /** The singular secret configured for the module: runtime config (`NUXT_BETTER_AUTH_SECRET`) or `BETTER_AUTH_SECRET`. */
2
+ export declare function getConfiguredAuthSecret(runtimeConfig: object): string;
3
+ /** Whether a secret source that predates `appSecret` derivation is configured. These always win. */
4
+ export declare function hasExplicitAuthSecret(runtimeConfig: object): boolean;
5
+ /**
6
+ * Whether the auth secret is derived from `appSecret`: Nuxt provides `deriveSecret()` (4.6+) and
7
+ * the `appSecret` is the user's. In development Nuxt generates an `appSecret` when none is set
8
+ * (`NUXT_APP_SECRET_GENERATED`); deriving from that would replace Better Auth's development
9
+ * default secret and sign out existing development sessions, so the default is kept.
10
+ */
11
+ export declare function canDeriveAuthSecret(): boolean;
12
+ /**
13
+ * Derives the Better Auth secret from Nuxt's `appSecret` once per server process, when no auth
14
+ * secret is configured and the `appSecret` is the user's (see `canDeriveAuthSecret`).
15
+ * `serverAuth()` is synchronous, so request entry points and the startup plugin start this, and
16
+ * the request entry points await it before they create the auth instance.
17
+ */
18
+ export declare function prepareAuthSecret(): Promise<void> | undefined;
19
+ export interface DerivedAuthSecretState {
20
+ /** The secret is derived from the user's `appSecret` (Nuxt 4.6+). */
21
+ available: boolean;
22
+ secret?: string;
23
+ error?: unknown;
24
+ }
25
+ export declare function getDerivedAuthSecret(): DerivedAuthSecretState;
@@ -0,0 +1,40 @@
1
+ import { env } from "better-auth";
2
+ import { deriveAuthSecret } from "./app-secret-compat.js";
3
+ import { useRuntimeConfig } from "./nitro-compat.js";
4
+ let derivedSecret;
5
+ let derivedSecretPromise;
6
+ let derivedSecretError;
7
+ export function getConfiguredAuthSecret(runtimeConfig) {
8
+ const secret = runtimeConfig.betterAuthSecret;
9
+ return typeof secret === "string" && secret || env.BETTER_AUTH_SECRET || "";
10
+ }
11
+ export function hasExplicitAuthSecret(runtimeConfig) {
12
+ return Boolean(getConfiguredAuthSecret(runtimeConfig) || env.BETTER_AUTH_SECRETS || env.AUTH_SECRET);
13
+ }
14
+ export function canDeriveAuthSecret() {
15
+ if (!deriveAuthSecret)
16
+ return false;
17
+ return !(import.meta.dev && globalThis.process?.env?.NUXT_APP_SECRET_GENERATED === "1");
18
+ }
19
+ export function prepareAuthSecret() {
20
+ if (derivedSecret !== void 0 || derivedSecretError !== void 0 || !canDeriveAuthSecret())
21
+ return;
22
+ if (hasExplicitAuthSecret(useRuntimeConfig()))
23
+ return;
24
+ derivedSecretPromise ||= deriveAuthSecret().then(
25
+ (secret) => {
26
+ derivedSecret = secret;
27
+ },
28
+ (error) => {
29
+ derivedSecretError = error ?? new Error("deriveSecret failed");
30
+ }
31
+ );
32
+ return derivedSecretPromise;
33
+ }
34
+ export function getDerivedAuthSecret() {
35
+ return {
36
+ available: canDeriveAuthSecret(),
37
+ secret: derivedSecret,
38
+ error: derivedSecretError
39
+ };
40
+ }
@@ -1,3 +1,4 @@
1
1
  import type { ServerEvent } from './nitro-compat.js';
2
2
  export declare function appendCookieHeader(event: ServerEvent, header: string): void;
3
+ export declare function getSetCookieHeaders(headers: Headers): string[];
3
4
  export declare function appendSetCookieHeaders(event: ServerEvent, headers: Headers): void;
@@ -14,7 +14,7 @@ export function appendCookieHeader(event, header) {
14
14
  const responseHeaders = eventWithResponse.res?.headers ?? eventWithResponse.response?.headers;
15
15
  responseHeaders?.append("set-cookie", header);
16
16
  }
17
- function getSetCookieHeaders(headers) {
17
+ export function getSetCookieHeaders(headers) {
18
18
  const cookieHeaders = headers;
19
19
  const getSetCookie = cookieHeaders.getSetCookie;
20
20
  if (getSetCookie)
@@ -1,10 +1,9 @@
1
- import type { H3Event } from 'nitro/h3';
1
+ import type { RequestEvent } from 'nuxt/server';
2
2
  import type { AuthRouteRules } from '../../types.js';
3
3
  import { splitSetCookieString } from 'cookie-es';
4
- import { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody } from 'nitro/h3';
5
- import { useRuntimeConfig } from 'nitro/runtime-config';
4
+ import { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody, useRuntimeConfig } from 'nuxt/server';
6
5
  export { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody, useRuntimeConfig, };
7
- export type ServerEvent = H3Event;
6
+ export type ServerEvent = RequestEvent;
8
7
  export { splitSetCookieString as splitCookiesString };
9
8
  export declare function getAuthRouteRules(event: ServerEvent): AuthRouteRules;
10
9
  export declare function createAuthError(status: number, statusText: string): Error;
@@ -1,15 +1,15 @@
1
1
  import { splitSetCookieString } from "cookie-es";
2
- import { getRouteRules } from "nitro/app";
3
2
  import {
3
+ createError,
4
4
  defineEventHandler,
5
5
  getQuery,
6
6
  getRequestHost,
7
7
  getRequestProtocol,
8
8
  getRequestURL,
9
- HTTPError,
10
- readBody
11
- } from "nitro/h3";
12
- import { useRuntimeConfig } from "nitro/runtime-config";
9
+ getRouteRules,
10
+ readBody,
11
+ useRuntimeConfig
12
+ } from "nuxt/server";
13
13
  import { normalizeAuthRouteRule } from "../../internal/auth-route-rules.js";
14
14
  export {
15
15
  defineEventHandler,
@@ -22,11 +22,11 @@ export {
22
22
  };
23
23
  export { splitSetCookieString as splitCookiesString };
24
24
  export function getAuthRouteRules(event) {
25
- const auth = getRouteRules(event.req.method, getRequestURL(event).pathname).routeRules?.auth;
26
- return { auth: normalizeAuthRouteRule(auth) };
25
+ const rules = getRouteRules(event);
26
+ return { auth: normalizeAuthRouteRule(rules.auth) };
27
27
  }
28
28
  export function createAuthError(status, statusText) {
29
- return HTTPError.status(status, statusText);
29
+ return createError({ status, statusText });
30
30
  }
31
31
  export function toWebRequest(event) {
32
32
  return event.req;
@@ -1,8 +1,10 @@
1
1
  import { normalizeAuthRoutePath, shouldSkipAuthRouteRules } from "../../internal/auth-route-rules.js";
2
2
  import { matchesUser } from "../../utils/match-user.js";
3
+ import { prepareAuthSecret } from "../internal/auth-secret.js";
3
4
  import { createAuthError, defineEventHandler, getAuthRouteRules, getRequestURL, useRuntimeConfig } from "../internal/nitro-compat.js";
4
5
  import { getUserSession, requireUserSession } from "../utils/session.js";
5
6
  export default defineEventHandler(async (event) => {
7
+ await prepareAuthSecret();
6
8
  const path = normalizeAuthRoutePath(
7
9
  getRequestURL(event).pathname,
8
10
  useRuntimeConfig().app?.baseURL
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Starts deriving the auth secret from Nuxt's `appSecret` when the server starts, so a
3
+ * `serverAuth()` call made before the first request, such as from a task, can use it.
4
+ * Nitro does not await plugins: startup code that needs the secret right away awaits
5
+ * `ensureServerAuth()` instead.
6
+ */
7
+ export default function authSecretPlugin(): void;
@@ -0,0 +1,4 @@
1
+ import { prepareAuthSecret } from "../internal/auth-secret.js";
2
+ export default function authSecretPlugin() {
3
+ void prepareAuthSecret();
4
+ }
@@ -4,6 +4,7 @@
4
4
  "module": "ESNext",
5
5
  "moduleResolution": "bundler",
6
6
  "paths": {
7
+ "#better-auth/app-secret": ["./internal/app-secret"],
7
8
  "#better-auth/nitro-compat": ["./internal/nitro2"],
8
9
  "#nuxt-better-auth": ["../types/augment"]
9
10
  },
@@ -16,6 +16,12 @@ type ResolvedAuthOptions = Omit<UserAuthConfig, 'secret' | 'baseURL' | 'trustedO
16
16
  database?: BetterAuthOptions['database'];
17
17
  };
18
18
  type AuthInstance = ReturnType<typeof betterAuth<ResolvedAuthOptions>>;
19
+ /**
20
+ * Returns the Better Auth instance like `serverAuth()`, after waiting for the auth secret that
21
+ * Nuxt 4.6+ derives from `appSecret` (`NUXT_APP_SECRET`). Use it in code that can run before the
22
+ * first request, such as a Nitro plugin or task at server startup.
23
+ */
24
+ export declare function ensureServerAuth(event?: ServerEvent): Promise<AuthInstance>;
19
25
  /** Returns Better Auth instance. Caches per resolved host (or single instance when siteUrl is explicit). */
20
26
  export declare function serverAuth(event?: ServerEvent): AuthInstance;
21
27
  export {};
@@ -1,7 +1,7 @@
1
1
  import { betterAuth, env } from "better-auth";
2
- import { withoutProtocol } from "ufo";
3
2
  import { createDatabase, db } from "#auth/database";
4
3
  import createServerAuth from "#auth/server";
4
+ import { getConfiguredAuthSecret, getDerivedAuthSecret, hasExplicitAuthSecret, prepareAuthSecret } from "../internal/auth-secret.js";
5
5
  import { getRequestHost, getRequestProtocol, useRuntimeConfig } from "../internal/nitro-compat.js";
6
6
  import { resolveAuthBaseURL } from "../../internal/auth-base-url.js";
7
7
  import { resolveCustomSecondaryStorageRequirement } from "./custom-secondary-storage.js";
@@ -11,6 +11,7 @@ let _baseURLInferenceLogged = false;
11
11
  let _customSecondaryStorageMisconfigWarned = false;
12
12
  let _unsupportedHubSecondaryStorageWarned = false;
13
13
  let _secondaryStorageRateLimitFallbackWarned = false;
14
+ let _derivedSecretFallbackWarned = false;
14
15
  const fallbackRequestAuthContext = /* @__PURE__ */ new WeakMap();
15
16
  function getRequestAuthContext(event) {
16
17
  const eventWithContext = event;
@@ -83,33 +84,25 @@ function getNitroOrigin() {
83
84
  let port;
84
85
  if (import.meta.dev)
85
86
  port = process.env.NITRO_PORT || process.env.PORT || "3000";
86
- let protocol = cert && key || !import.meta.dev ? "https" : "http";
87
+ const protocol = cert && key || !import.meta.dev ? "https" : "http";
87
88
  try {
88
89
  if ((import.meta.dev || import.meta.prerender) && process.env.__NUXT_DEV__) {
89
- const origin = JSON.parse(process.env.__NUXT_DEV__).proxy.url;
90
- host = withoutProtocol(origin);
91
- protocol = origin.includes("https") ? "https" : "http";
90
+ const url = new URL(JSON.parse(process.env.__NUXT_DEV__).proxy.url);
91
+ if (url.protocol === "http:" || url.protocol === "https:")
92
+ return url.origin;
92
93
  } else if ((import.meta.dev || import.meta.prerender) && process.env.NUXT_VITE_NODE_OPTIONS) {
93
- const origin = JSON.parse(process.env.NUXT_VITE_NODE_OPTIONS).baseURL.replace("/__nuxt_vite_node__", "");
94
- host = withoutProtocol(origin);
95
- protocol = origin.includes("https") ? "https" : "http";
94
+ const url = new URL(JSON.parse(process.env.NUXT_VITE_NODE_OPTIONS).baseURL);
95
+ if (url.protocol === "http:" || url.protocol === "https:")
96
+ return url.origin;
96
97
  }
97
98
  } catch {
98
99
  }
99
100
  if (!host)
100
101
  return void 0;
101
- if (host.startsWith("[") && host.includes("]:")) {
102
- const lastBracketColon = host.lastIndexOf("]:");
103
- const extractedPort = host.slice(lastBracketColon + 2);
104
- host = host.slice(0, lastBracketColon + 1);
105
- if (extractedPort)
106
- port = extractedPort;
107
- } else if (host.includes(":") && !host.startsWith("[")) {
108
- const hostParts = host.split(":");
109
- port = hostParts.pop();
110
- host = hostParts.join(":");
111
- }
112
- const portSuffix = port ? `:${port}` : "";
102
+ if (!host.startsWith("[") && host.indexOf(":") !== host.lastIndexOf(":"))
103
+ host = `[${host}]`;
104
+ const hasExplicitPort = host.startsWith("[") ? host.includes("]:") : host.includes(":");
105
+ const portSuffix = !hasExplicitPort && port ? `:${port}` : "";
113
106
  return `${protocol}://${host}${portSuffix}`;
114
107
  }
115
108
  function resolveEnvironmentOrigin() {
@@ -162,8 +155,7 @@ function getDevTrustedOrigins() {
162
155
  try {
163
156
  const url = new URL(nitroOrigin);
164
157
  const protocol = url.protocol === "https:" ? "https" : "http";
165
- const port = url.port || "3000";
166
- const localhostOrigin = `${protocol}://localhost:${port}`;
158
+ const localhostOrigin = `${protocol}://localhost${url.port ? `:${url.port}` : ""}`;
167
159
  return dedupeOrigins([localhostOrigin, url.origin]);
168
160
  } catch {
169
161
  return [fallbackOrigin];
@@ -203,17 +195,39 @@ function withDevTrustedOrigins(trustedOrigins) {
203
195
  return mergeOrigins([], request);
204
196
  };
205
197
  }
198
+ function missingAuthSecretMessage(derived) {
199
+ const message = "[nuxt-better-auth] An auth secret is required in production. Set NUXT_BETTER_AUTH_SECRET, BETTER_AUTH_SECRET, BETTER_AUTH_SECRETS, or defineServerAuth({ secrets })";
200
+ if (!derived.available)
201
+ return `${message}.`;
202
+ if (derived.error !== void 0)
203
+ return `${message}, or set NUXT_APP_SECRET (at least 32 characters) to derive one from Nuxt's appSecret.`;
204
+ return `${message}. The secret derived from NUXT_APP_SECRET is resolved asynchronously, and serverAuth() was called before it was ready, outside a request. Use \`await ensureServerAuth()\` in code that runs at server startup.`;
205
+ }
206
+ function warnDerivedSecretFallback(derived) {
207
+ if (_derivedSecretFallbackWarned)
208
+ return;
209
+ _derivedSecretFallbackWarned = true;
210
+ const reason = derived.error !== void 0 ? "could not be derived from NUXT_APP_SECRET. Set NUXT_APP_SECRET (at least 32 characters) or NUXT_BETTER_AUTH_SECRET." : "derived from NUXT_APP_SECRET was not ready yet. Use `await ensureServerAuth()` in code that runs at server startup.";
211
+ console.warn(`[nuxt-better-auth] Better Auth's development default secret is used for this call: the auth secret ${reason}`);
212
+ }
213
+ export async function ensureServerAuth(event) {
214
+ await prepareAuthSecret();
215
+ return serverAuth(event);
216
+ }
206
217
  export function serverAuth(event) {
207
218
  const runtimeConfig = useRuntimeConfig();
208
- const betterAuthSecret = runtimeConfig.betterAuthSecret || env.BETTER_AUTH_SECRET || "";
209
- if (betterAuthSecret && betterAuthSecret.length < 32)
219
+ const configuredSecret = getConfiguredAuthSecret(runtimeConfig);
220
+ if (configuredSecret && configuredSecret.length < 32)
210
221
  throw new Error("[nuxt-better-auth] Singular auth secret must be at least 32 characters for security");
222
+ const derived = getDerivedAuthSecret();
211
223
  const requestOrigin = resolveEventOrigin(event);
212
224
  let userConfig;
213
- if (!import.meta.dev && !betterAuthSecret && !env.BETTER_AUTH_SECRETS) {
225
+ if (!import.meta.dev && !configuredSecret && !env.BETTER_AUTH_SECRETS && !derived.secret) {
214
226
  userConfig = createServerAuth({ runtimeConfig, db, requestOrigin });
215
- if (userConfig.secrets === void 0)
216
- throw new Error("[nuxt-better-auth] An auth secret is required in production. Set NUXT_BETTER_AUTH_SECRET, BETTER_AUTH_SECRET, BETTER_AUTH_SECRETS, or defineServerAuth({ secrets }).");
227
+ if (userConfig.secrets === void 0) {
228
+ void prepareAuthSecret();
229
+ throw new Error(missingAuthSecretMessage(derived));
230
+ }
217
231
  }
218
232
  const siteUrl = getBaseURL(event);
219
233
  const hasExplicitSiteUrl = runtimeConfig.public.siteUrl && typeof runtimeConfig.public.siteUrl === "string";
@@ -222,6 +236,13 @@ export function serverAuth(event) {
222
236
  if (requestContext?.[requestAuthKey])
223
237
  return requestContext[requestAuthKey];
224
238
  userConfig ??= createServerAuth({ runtimeConfig, db, requestOrigin });
239
+ const usesDerivedSecret = derived.available && userConfig.secrets === void 0 && !hasExplicitAuthSecret(runtimeConfig);
240
+ const derivedSecretMissing = usesDerivedSecret && !derived.secret;
241
+ if (derivedSecretMissing) {
242
+ void prepareAuthSecret();
243
+ warnDerivedSecretFallback(derived);
244
+ }
245
+ const betterAuthSecret = configuredSecret || (usesDerivedSecret ? derived.secret ?? "" : "");
225
246
  const database = createDatabase(event);
226
247
  const trustedOrigins = withDevTrustedOrigins(userConfig.trustedOrigins);
227
248
  const hubSecondaryStorage = runtimeConfig.auth?.hubSecondaryStorage;
@@ -242,7 +263,8 @@ export function serverAuth(event) {
242
263
  console.warn('[nuxt-better-auth] rateLimit.storage: "secondary-storage" requires secondaryStorage. Falling back to process-local memory. Set rateLimit.storage to "database" or provide rateLimit.customStorage for shared limits.');
243
264
  }
244
265
  const rateLimit = useMemoryRateLimit ? { ...userConfig.rateLimit, storage: "memory" } : userConfig.rateLimit;
245
- if (!database) {
266
+ const cacheable = !database && !derivedSecretMissing;
267
+ if (cacheable) {
246
268
  const cached = _authCache.get(cacheKey);
247
269
  if (cached) {
248
270
  if (requestContext)
@@ -261,7 +283,7 @@ export function serverAuth(event) {
261
283
  const auth = betterAuth(authOptions);
262
284
  if (requestContext)
263
285
  requestContext[requestAuthKey] = auth;
264
- if (!database)
286
+ if (cacheable)
265
287
  _authCache.set(cacheKey, auth);
266
288
  return auth;
267
289
  }
@@ -1,6 +1,7 @@
1
1
  import { matchesUser } from "../../utils/match-user.js";
2
2
  import { createAuthError } from "../internal/nitro-compat.js";
3
- import { appendCookieHeader, appendSetCookieHeaders } from "../internal/cookie-headers.js";
3
+ import { prepareAuthSecret } from "../internal/auth-secret.js";
4
+ import { appendCookieHeader, appendSetCookieHeaders, getSetCookieHeaders } from "../internal/cookie-headers.js";
4
5
  import { serverAuth } from "./auth.js";
5
6
  const requestSessionLoadKey = Symbol.for("nuxt-better-auth.requestSessionLoad");
6
7
  const requestSessionMutationKey = Symbol.for("nuxt-better-auth.requestSessionMutation");
@@ -26,6 +27,9 @@ function getRequestHeaders(event) {
26
27
  return getRequestSessionContext(event).requestHeaders ?? getIncomingRequestHeaders(event);
27
28
  }
28
29
  async function loadSession(event) {
30
+ const pendingSecret = prepareAuthSecret();
31
+ if (pendingSecret)
32
+ await pendingSecret;
29
33
  const auth = serverAuth(event);
30
34
  const result = await auth.api.getSession({
31
35
  headers: getRequestHeaders(event),
@@ -36,6 +40,9 @@ async function loadSession(event) {
36
40
  return { headers: new Headers(), response: result };
37
41
  }
38
42
  function loadFreshSession(event) {
43
+ const pendingSecret = prepareAuthSecret();
44
+ if (pendingSecret)
45
+ return pendingSecret.then(() => loadFreshSession(event));
39
46
  const auth = serverAuth(event);
40
47
  return auth.api.getSession({
41
48
  headers: getRequestHeaders(event),
@@ -44,6 +51,9 @@ function loadFreshSession(event) {
44
51
  });
45
52
  }
46
53
  function getServerAuthContext(event) {
54
+ const pendingSecret = prepareAuthSecret();
55
+ if (pendingSecret)
56
+ return pendingSecret.then(() => getServerAuthContext(event));
47
57
  const auth = serverAuth(event);
48
58
  return auth.$context;
49
59
  }
@@ -162,6 +172,30 @@ function expireCookies(event, cookie) {
162
172
  for (const cookieName of getChunkedCookieNames(event, cookie.name))
163
173
  expireCookie(event, cookieName, cookie.attributes);
164
174
  }
175
+ function isCookieOrChunk(name, cookieName) {
176
+ if (name === cookieName)
177
+ return true;
178
+ if (!name.startsWith(`${cookieName}.`))
179
+ return false;
180
+ const suffix = name.slice(cookieName.length + 1);
181
+ const index = Number(suffix);
182
+ return Number.isSafeInteger(index) && index >= 0 && String(index) === suffix;
183
+ }
184
+ function expireUnrefreshedSessionCookieCache(event, headers, cookie) {
185
+ const cookieName = getCookieName(cookie.name, cookie.attributes.prefix);
186
+ if (!cookieName)
187
+ return;
188
+ const responseHandlesCache = getSetCookieHeaders(headers).some((header) => {
189
+ const name = header.slice(0, header.indexOf("="));
190
+ return isCookieOrChunk(name, cookieName);
191
+ });
192
+ if (responseHandlesCache)
193
+ return;
194
+ for (const name of parseRequestCookies(getRequestHeaders(event).get("cookie")).keys()) {
195
+ if (isCookieOrChunk(name, cookieName))
196
+ expireCookie(event, name, cookie.attributes);
197
+ }
198
+ }
165
199
  function updateRequestHeaders(event, sessionCookie, clearedCookieNames) {
166
200
  const requestContext = getRequestSessionContext(event);
167
201
  const incomingHeaders = getIncomingRequestHeaders(event);
@@ -227,10 +261,14 @@ export async function refreshSessionCookieCache(event) {
227
261
  if (context[requestSessionLoadKey] !== load)
228
262
  return context.requestSession ?? null;
229
263
  delete context.requestSession;
230
- const { headers, response } = await loadFreshSession(event);
264
+ const [{ headers, response }, authContext] = await Promise.all([
265
+ loadFreshSession(event),
266
+ getServerAuthContext(event)
267
+ ]);
231
268
  if (context[requestSessionLoadKey] !== load)
232
269
  return context.requestSession ?? null;
233
270
  appendSetCookieHeaders(event, headers);
271
+ expireUnrefreshedSessionCookieCache(event, headers, authContext.authCookies.sessionData);
234
272
  context.requestSession = response;
235
273
  return response;
236
274
  });
@@ -1,9 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { fileURLToPath } from "node:url";
3
- import { url } from "@nuxt/test-utils/e2e";
3
+ import { url, useTestContext } from "@nuxt/test-utils/e2e";
4
4
  import { parseJSON } from "better-auth/client";
5
5
  import { getRandomPort } from "get-port-please";
6
6
  import { defu } from "defu";
7
+ import { joinURL } from "ufo";
7
8
  export async function createAuthTestContext(options = {}) {
8
9
  if (options.host || options.dev || options.build === false || options.server === false)
9
10
  throw new Error("[nuxt-better-auth] Auth E2E helpers require a local test build. Use setup() with build and server enabled, without dev or host.");
@@ -22,7 +23,8 @@ export async function createAuthTestContext(options = {}) {
22
23
  }
23
24
  }, options);
24
25
  async function call(action, data = {}) {
25
- const response = await fetch(url(endpoint), {
26
+ const baseURL = useTestContext().nuxt?.options.app.baseURL ?? "/";
27
+ const response = await fetch(url(joinURL(baseURL, endpoint)), {
26
28
  method: "POST",
27
29
  headers: { "content-type": "application/json", "x-nuxt-auth-test": token },
28
30
  body: JSON.stringify({ action, data })
@@ -9,7 +9,7 @@ const requestSchema = z.discriminatedUnion("action", [
9
9
  ]);
10
10
  const ownedUsers = /* @__PURE__ */ new Set();
11
11
  export default defineEventHandler(async (event) => {
12
- const token = useRuntimeConfig(event).betterAuthTestToken;
12
+ const token = useRuntimeConfig().betterAuthTestToken;
13
13
  if (!token || toWebRequest(event).headers.get("x-nuxt-auth-test") !== token)
14
14
  throw createAuthError(404, "Not found");
15
15
  const body = requestSchema.parse(parseJSON(await toWebRequest(event).text()));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nuxtjs/better-auth",
3
3
  "type": "module",
4
- "version": "0.3.6",
4
+ "version": "0.3.8",
5
5
  "description": "Nuxt module for Better Auth integration with NuxtHub, route protection, session management, and role-based access",
6
6
  "author": "onmax",
7
7
  "license": "MIT",
@@ -73,9 +73,9 @@
73
73
  },
74
74
  "dependencies": {
75
75
  "@nuxt/devtools-kit": "^3.4.1",
76
- "@nuxt/kit": "^4.5.2",
76
+ "@nuxt/kit": "^4.6.0",
77
77
  "@types/node": "^24.13.3",
78
- "auth": "1.7.3",
78
+ "auth": "1.7.7",
79
79
  "consola": "^3.4.2",
80
80
  "cookie-es": "^3.1.1",
81
81
  "defu": "^6.1.7",
@@ -91,36 +91,36 @@
91
91
  },
92
92
  "devDependencies": {
93
93
  "@antfu/eslint-config": "^9.3.0",
94
- "@libsql/client": "^0.17.4",
94
+ "@libsql/client": "^0.18.0",
95
95
  "@libsql/linux-x64-gnu": "0.5.29",
96
96
  "@nuxt/devtools": "^3.4.1",
97
97
  "@nuxt/fonts": "0.14.0",
98
98
  "@nuxt/module-builder": "^1.0.3",
99
- "@nuxt/schema": "^4.5.2",
100
- "@nuxt/test-utils": "^4.1.0",
99
+ "@nuxt/schema": "^4.6.0",
100
+ "@nuxt/test-utils": "^4.3.3",
101
101
  "@nuxthub/core": "^0.10.8",
102
102
  "@pinia/nuxt": "^1.0.2",
103
103
  "@types/better-sqlite3": "^9.6.0",
104
104
  "@vue/test-utils": "^2.4.6",
105
- "better-auth": "1.7.3",
105
+ "better-auth": "1.7.7",
106
106
  "better-call": "1.4.0",
107
- "better-sqlite3": "^12.11.1",
107
+ "better-sqlite3": "^13.0.0",
108
108
  "drizzle-kit": "^0.31.10",
109
109
  "drizzle-orm": "^0.45.2",
110
110
  "eslint": "^10.8.1",
111
111
  "happy-dom": "^20.8.9",
112
112
  "nitropack": "2.13.4",
113
- "nuxt": "^4.5.2",
113
+ "nuxt": "^4.6.0",
114
114
  "pinia": "^4.0.3",
115
115
  "pkg-pr-new": "0.0.88",
116
- "playwright-core": "1.58.2",
116
+ "playwright-core": "1.63.0",
117
117
  "postgres": "^3.4.9",
118
118
  "typescript": "~5.9.3",
119
119
  "unstorage": "^1.17.5",
120
- "vitest": "^4.1.11",
121
- "vue": "3.5.41",
120
+ "vitest": "^5.0.0",
121
+ "vue": "3.5.43",
122
122
  "vue-tsc": "^3.3.10",
123
- "wrangler": "4.124.0",
123
+ "wrangler": "4.147.0",
124
124
  "yaml": "^2.9.0"
125
125
  },
126
126
  "scripts": {