Architecture de l'authentification et de l'admin
Guide approfondi sur la gestion des sessions Better Auth, OAuth, RBAC et la modélisation des données métier (crédits et quotas) dans ce template.
Authentification & Admin
Ce document est la référence principale pour comprendre le fonctionnement de l'authentification et de l'autorisation admin dans ce template.
Il couvre :
- Le modèle Better Auth exact utilisé ici
- Le fonctionnement des sessions avec email/mot de passe et OAuth Google
- Pourquoi
users,accounts,sessionsetverificationssont séparées - Ce qui relève des données d'auth vs des données métier (crédits, quotas, permissions)
- L'implémentation du RBAC admin dans ce codebase
1) Modèle d'authentification utilisé dans ce template
Ce projet utilise des sessions gérées côté serveur, basées sur des cookies et stockées en Postgres. Ce n'est pas un modèle JWT-first par défaut.
Pourquoi :
- Better Auth est configuré avec l'adaptateur Drizzle + Postgres dans
src/lib/auth.ts - Le stockage des sessions est mappé à la table
sessionsdanssrc/db/schema.ts - Le plugin
nextCookies()est activé - Aucun plugin JWT n'est activé comme mécanisme principal
Implication pratique :
- Le navigateur stocke un cookie d'auth HttpOnly (token de session)
- Le serveur valide ce token contre
sessions - La ligne de session pointe vers l'identité utilisateur
Le support JWT existe dans Better Auth, mais il est opt-in et ne remplace pas le modèle de session DB utilisé ici.
2) Tables d'authentification principales et responsabilités
Better Auth sépare volontairement identité, identifiants et état de connexion.
users (profil d'identité)
Représente qui est la personne dans votre produit.
Champs typiques :
- Profil (
nickname,avatar_url) - Rôle (
role) - Drapeaux de vérification/statut
- Timestamps de création/mise à jour
Dans ce template, des champs additionnels sont mappés :
uuid(généré à la création)role
Cette table doit rester relativement stable.
accounts (méthodes d'authentification)
Représente comment l'utilisateur peut se connecter.
- Un utilisateur peut avoir plusieurs comptes/providers
- Stocke l'identité provider et les tokens provider
Champs typiques :
provider_idaccount_id- Hash de mot de passe (email/mot de passe)
- Access token / refresh token OAuth
- Expiration des tokens OAuth
Cette table est orientée identifiants et sensible côté sécurité.
sessions (état de connexion)
Représente les sessions actives dans votre app.
Champs typiques :
user_idtokenexpires_atip_addressuser_agent
Propriétés :
- L'expiration de session contrôle l'état connecté
- La révocation se fait en supprimant/invalidant des lignes
- Le renouvellement glissant est piloté par la policy de session
verifications (flux one-shot)
Utilisée pour des tokens temporaires dans des flux comme :
- Réinitialisation de mot de passe
- Vérification email
- Magic link / OTP (si activé)
La connexion/déconnexion classique n'écrit pas en continu ici, donc voir peu de lignes est normal.
3) Procédure d'authentification de bout en bout dans ce codebase
3.1 Inscription Email + mot de passe
- Le client soumet le formulaire d'inscription
- Better Auth crée une ligne
users databaseHooks.user.create.beforedanssrc/lib/auth.tsgarantit la présence deuuid- Better Auth crée une ligne
accountspour email/mot de passe - Better Auth crée une ligne
sessions - Le cookie de session est envoyé dans la réponse
databaseHooks.user.create.afterdéclenche l'email de bienvenue en asynchrone
Résultat : l'utilisateur est connecté immédiatement.
3.2 Connexion Email + mot de passe
- Le client soumet ses identifiants
- Better Auth valide le hash de mot de passe dans
accounts - Better Auth crée/rafraîchit la session dans
sessions - Le cookie de session est créé/mis à jour
3.3 Connexion OAuth Google
- L'utilisateur clique sur Google
- Le flux redirect + callback OAuth s'exécute
- Google renvoie l'identité du compte (
sub) et des claims profil - Better Auth trouve ou crée l'utilisateur
- Better Auth crée/met à jour la ligne
accountspourgoogle - Better Auth crée une ligne de session
- Le cookie de session est envoyé
4) Règle importante sur l'identité OAuth
La connexion Google ne nécessite pas une adresse Gmail.
- Les utilisateurs peuvent utiliser Gmail, Outlook, Yahoo ou un domaine perso lié à un compte Google
- L'identité doit être basée sur l'identité provider (
provider_id,account_id/sub), pas sur le domaine email
Ne jamais traiter le domaine email comme preuve de provider.
5) Expiration de session vs expiration des tokens OAuth
Ce sont deux cycles de vie distincts.
Cycle de vie session app (sessions)
- Contrôle l'état connecté dans votre app
- Si la session expire,
getSession()retourne null - L'utilisateur doit se reconnecter
Cycle de vie token provider (accounts)
- Contrôle la capacité backend à appeler les APIs provider
- Les access tokens expirent rapidement
- Les refresh tokens peuvent servir à régénérer un access token
Point critique :
- L'expiration Better Auth ne reconnecte pas automatiquement via refresh token Google
- Le refresh token sert aux appels API provider, pas à l'authentification app
6) Pourquoi sessions et tokens OAuth doivent rester séparés
Cette séparation est nécessaire pour une sécurité prévisible :
- Un refresh token OAuth n'est pas une preuve d'identité app
- La révocation de session doit rester effective
- La ré-auth explicite doit rester possible
- Les frontières de sécurité/audit restent claires
Recréer automatiquement des sessions app via refresh token affaiblirait la révocation et l'auditabilité.
7) Autorisation (RBAC) dans ce template
L'authentification répond à "qui êtes-vous". L'autorisation répond à "que pouvez-vous faire".
Les rôles sont pilotés par la base via users.role :
useradmin_roadmin_rw
Les checks de rôle sont dans src/lib/authz.ts :
requireAdminRead()-> autoriseadmin_roetadmin_rwrequireAdminWrite()-> autoriseadmin_rw
Garde de layout admin : src/app/(admin)/layout.tsx.
8) Surfaces admin et APIs actuelles
Pages admin
- Dashboard :
src/app/(admin)/admin/page.tsx - Liste utilisateurs :
src/app/(admin)/admin/users/page.tsx - Détail utilisateur/quota/crédits :
src/app/(admin)/admin/users/[uuid]/page.tsx - Liste commandes/statuts :
src/app/(admin)/admin/orders/page.tsx - Feedbacks :
src/app/(admin)/admin/feedbacks/page.tsx - Réservations :
src/app/(admin)/admin/reservations/page.tsx - Affiliés :
src/app/(admin)/admin/affiliates/page.tsx
Routes API admin
GET /api/admin/users-> liste paginée utilisateursGET /api/admin/users/[uuid]-> détail utilisateur + usage mensuel + résumé créditsPATCH /api/admin/users/[uuid]-> met à jourmonthlyCreditsQuotaGET /api/admin/users/[uuid]/credits-> résumé crédits + ledgerPOST /api/admin/credits/grant-> accorde des créditsPOST /api/admin/credits/adjust-> ajustement positif/négatif de créditsGET /api/admin/orders-> commandes paginées avec filtre statut (all|paid|created|deleted)
9) Sessions cookie vs JWT (ce que vous utilisez réellement)
Configuration actuelle :
- Le cookie de session contient un token de session
- Le serveur vérifie le token contre la table
sessions - L'utilisateur est résolu via session DB-backed
Donc c'est un modèle de session en base, pas un modèle JWT stateless pur.
Modèle JWT pur (pas le défaut actuel) :
- Le token porte lui-même les claims
- Le serveur vérifie la signature à chaque requête
- En général, pas de lookup DB nécessaire pour la validation de base
10) Pourquoi BETTER_AUTH_SECRET reste nécessaire
Même avec des tokens de session aléatoires, un secret reste nécessaire pour les flux de signature/vérification cryptographique.
Better Auth résout le secret dans cet ordre :
- Option
secretexplicite dans la config BETTER_AUTH_SECRETAUTH_SECRET- Fallback interne (pratique en dev, non sûr en prod)
Donc l'app peut démarrer sans secret env, mais ce n'est pas une posture sûre en production.
Utilisez un secret fort et stable par environnement. Changer ce secret peut invalider des artefacts d'auth signés et déconnecter les utilisateurs.
11) Modélisation des données métier : ce qui doit rester hors des tables auth
N'ajoutez pas des compteurs métier à forte fréquence de mise à jour dans les tables auth.
Garder les tables auth centrées auth
users: identité/profil/rôleaccounts: providers de login + identifiants/tokenssessions: état de connexionverifications: tokens one-shot de vérification
Placer l'état produit/facturation dans des tables de domaine
Pattern recommandé :
- Table d'état courant (lecture rapide)
- Ledger/events append-only (audit/rebuild)
- Table d'entitlements (permissions de features)
Exemples de structures :
-
user_balanceuser_id(PK)credits_balancequota_limitquota_usedplan_idbilling_period_startbilling_period_endupdated_at
-
usage_eventsiduser_idtypeamountcost_creditsmetadata(JSON)created_at
-
entitlementsuser_idkeyvaluesource(plan/admin/promo)expires_at
Cette séparation garde l'auth stable et la logique métier auditable.
12) Quotas, crédits et sécurité transactionnelle
Quand vous dépensez des crédits/quotas :
- Utilisez des transactions DB
- Vérifiez le solde/quota disponible
- Insérez un événement de ledger d'usage
- Appliquez la mise à jour de solde de façon conditionnelle
Cela évite les race conditions et les soldes négatifs en concurrence.
Dans ce template :
- L'usage des tâches est suivi dans
tasks - Le ledger de crédits est suivi dans
credits - Le quota mensuel (
users.monthly_credits_quota) est vérifié avant dépense de crédits
13) Compatibilité future : organizations
Si vous prévoyez des comptes équipe :
- Ajoutez
organizations - Déplacez balance/quota/entitlements au niveau organisation
- Traitez les comptes personnels comme des orgs mono-membre
Concevoir tôt pour ce modèle évite des refactors coûteux.
14) Checklist d'environnement et de setup
Variables auth minimales :
BETTER_AUTH_URLNEXT_PUBLIC_AUTH_BASE_URLBETTER_AUTH_SECRET(aléatoire et fort)GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET(si Google activé)
Si le schéma a changé, lancez les migrations :
pnpm drizzle-kit generate --config src/db/config.ts
pnpm drizzle-kit migrate --config src/db/config.tsRésumé final
- Ce projet utilise des sessions DB via cookies (pas JWT-first)
- L'identité OAuth est basée sur l'ID du compte provider, pas le domaine email
- Le cycle session app et le cycle token provider sont séparés
- Les tables auth doivent rester auth-only
- Crédits/quotas/permissions doivent vivre dans des tables de domaine avec ledger
- Le RBAC admin est basé sur le rôle DB et gardé côté serveur
Cette architecture apporte des frontières de sécurité solides, un meilleur contrôle opérationnel et une base claire pour scaler.
Démarrage rapide
Lancez le template Sushi SaaS en local avec pnpm, explorez les routes i18n, les health checks et les blogs MDX, puis voyez où configurer l’authentification, la facturation et la documentation.
Configuration Stripe
Utilisez Stripe Checkout pour les paiements et finalisez via webhook pour octroyer des crédits. Configurez les variables d’environnement, créez des sessions, gérez les callbacks et traitez les événements signés.