Skip to content

Correction de l'erreur Better Auth 'Model session not found in schema'

Correction de l'erreur Better Auth 'Model session not found in schema'

Analyse approfondie et résolution de l'erreur de schéma Better Auth avec Cloudflare Workers, KV Secondary Storage, Dash Plugin et Drizzle Adapter

1. Description du Problème

Lors des appels aux endpoints d’authentification (/api/auth/sign-in/email, /api/auth/dash/*), le worker Cloudflare amn-dev ou le serveur local renvoyait l’erreur critique suivante :

ERROR [Better Auth]: BetterAuthError BetterAuthError: Model "session" not found in schema
# SERVER_ERROR: BetterAuthError: Model "session" not found in schema

2. Analyse des Causes Racines

L’investigation approfondie de @better-auth/core (getDefaultModelName.mjs), @better-auth/drizzle-adapter et du plugin dash() (@better-auth/infra) a révélé deux causes racines combinées :

Cause 1 : Module Namespace vs Objet de Schéma Destructuré dans drizzleAdapter1/2

Better Auth résout les noms des modèles internes (user, session, account, verification) en recherchant les clés correspondantes dans l’objet schema fourni à l’adaptateur :

const getDefaultModelName = (model) => {
const resolve = (candidate) => {
if (schema[candidate]) return candidate;
return Object.entries(schema).find(([_, f]) => f.modelName === candidate)?.[0];
};
const m = resolve(model);
if (!m) throw new BetterAuthError(`Model "${model}" not found in schema`);
return m;
};

Cause 1 : Module Namespace vs Objet de Schéma Destructuré dans drizzleAdapter2/2

Lorsque le schéma est importé via import * as schema from "../db/schema", l’objet transmis est un ES Module Namespace contenant des métadonnées de module et des objets relations Drizzle en premier niveau. Cela perturbe la résolution de getDefaultModelName dans l’adaptateur Drizzle.

Cause 2 : Interaction entre secondaryStorage (KV) et le plugin dash()

Lorsque secondaryStorage (Cloudflare KV via createKVStorage(env.AUTH_KV)) est activé sans l’option storeSessionInDatabase: true :

  1. Better Auth délègue la gestion des sessions au KV et désactive l’enregistrement du modèle session dans le schéma de l’adaptateur de base de données (D1).
  2. Cependant, le plugin dash() (Better Auth Infrastructure, activé lorsque BETTER_AUTH_API_KEY est présent sur Cloudflare) effectue des appels internes de gestion et d’analyse sur l’adaptateur DB (ctx.context.adapter.findMany({ model: "session" })).
  3. L’adaptateur DB ne trouvant pas le modèle session dans son schéma interne, lève immédiatement l’exception Model "session" not found in schema.

3. Solution Appliquée (DOC → SPEC → CODE → TEST → INTÉG)

La correction a été appliquée dans apps/web/src/auth/index.ts :

A. Passage propre du schéma Drizzle

Importation déstructurée des tables et relations Drizzle :

import {
user,
session,
account,
verification,
userRelations,
sessionRelations,
accountRelations
} from "../db/schema";

B. Activation de storeSessionInDatabase: true

Garantit la présence continue du modèle session dans le schéma de l’adaptateur DB même si Cloudflare KV est utilisé en stockage secondaire :

session: {
modelName: "session",
expiresIn: 60 * 60 * 24 * 7, // 7 jours
updateAge: 60 * 60 * 24, // 1 jour
storeSessionInDatabase: true // Maintenance du schéma DB pour le plugin dash()
},

C. Déclaration explicite des modelName

Déclaration explicite des noms de modèles dans les options globales de betterAuth :

user: { modelName: "user" },
session: { modelName: "session", ... },
account: { modelName: "account", accountLinking: { enabled: false } },
verification: { modelName: "verification" }

Code complet dans apps/web/src/auth/index.ts1/2

Code complet dans apps/web/src/auth/index.ts2/2

export function createAuth(env: any) {
const db = drizzle(env.AUTH_DB, {
schema: { user, session, account, verification }
});
const isProd = env.ENVIRONMENT === 'prod';
const trustedOriginsEnv = env.BETTER_AUTH_TRUSTED_ORIGINS || '';
const trustedOrigins = trustedOriginsEnv
? trustedOriginsEnv.split(',').map((o: string) => o.trim())
: [];
if (!isProd) {
trustedOrigins.push('http://localhost:4321', 'http://localhost:8787');
}
const plugins: any[] = [admin()];
if (env.BETTER_AUTH_API_KEY) {
plugins.push(dash({
apiKey: env.BETTER_AUTH_API_KEY,
activityTracking: {
enabled: true,
updateInterval: 300000 // 5 minutes
}
}));
}
return betterAuth({
database: drizzleAdapter(db, {
provider: "sqlite",
schema: {
user,
session,
account,
verification,
userRelations,
sessionRelations,
accountRelations,
},
usePlural: false
}),
secondaryStorage: env.AUTH_KV ? createKVStorage(env.AUTH_KV) : undefined,
baseURL: env.BETTER_AUTH_URL || (isProd ? 'https://amn-dev.anderson-metsanou.workers.dev/api/auth' : 'http://localhost:8787/api/auth'),
trustedOrigins,
user: {
modelName: "user",
},
session: {
modelName: "session",
expiresIn: 60 * 60 * 24 * 7,
updateAge: 60 * 60 * 24,
storeSessionInDatabase: true
},
account: {
modelName: "account",
accountLinking: {
enabled: false
}
},
verification: {
modelName: "verification",
},
emailAndPassword: {
enabled: true,
requireEmailVerification: false
},
plugins
});
}

4. Tests et Validation Empirique

A. Contrôle de Qualité Code

Terminal window
task typecheck # 0 erreurs, 0 avertissements sur 55 fichiers
npx vitest run # 3/3 tests unitaires passés

B. Validation Locale (Wrangler Dev - http://localhost:8787)

  1. Connexion (/api/auth/sign-in/email) : HTTP 200 OK, génération du cookie amn-auth.session_token.
  2. Récupération de session (/api/auth/get-session) : HTTP 200 OK, retour de la session et des données utilisateur.

C. Validation Distante (Cloudflare Worker amn-dev)1/2

Terminal window
curl -i -X POST https://amn-dev.anderson-metsanou.workers.dev/api/auth/sign-in/email \
-H "Content-Type: application/json" \
-d '{"email":"andsinusmomega@gmail.com","password":"Debian13"}'

Résultat : HTTP/2 200 OK

C. Validation Distante (Cloudflare Worker amn-dev)2/2

{
"redirect": false,
"token": "9Ng3ypYV0f6Ix1tWSAU7sUl4wiOxcpsq",
"user": {
"id": "mWic8NK0CANSa22hxCHS5ZMdGuLZlYfS",
"name": "omega1",
"email": "andsinusmomega@gmail.com",
"emailVerified": true,
"role": "user"
}
}

5. Recommandations et Bonnes Pratiques

  1. Toujours définir storeSessionInDatabase: true lorsque secondaryStorage est utilisé conjointement avec des plugins de gestion d’infrastructure (dash()).
  2. Passer un schéma déstructuré à drizzleAdapter plutôt qu’un import d’espace de nommage (import * as schema).
  3. Spécifier les modelName explicites dans la configuration betterAuth lors de l’utilisation de tables au singulier (usePlural: false).
Switch to normal mode

Contents