L’authentification est la brique que tout le monde sous-estime : sessions, rotation des jetons, liaison de comptes, limitation de débit, double facteur, et une dizaine de cas limites qui n’apparaissent qu’en production. La réponse habituelle est un service hébergé, qui échange ce travail contre un abonnement mensuel, une table utilisateur imposée et une intégration illisible. Better Auth prend le chemin inverse : une bibliothèque TypeScript indépendante du framework qui place toute la couche d’authentification dans votre dépôt, contre votre propre base de données, avec des types qui circulent de la configuration serveur jusqu’à l’appel client.
Une seule configuration serveur
Tout part d’un appel à betterAuth qui déclare l’adaptateur de base de données et les méthodes acceptées. Aucun tableau de bord hébergé ne duplique cette configuration, et aucune dérive n’est possible entre les deux, puisque l’objet ci-dessous est l’unique source de vérité :
import { betterAuth } from 'better-auth';
import { prismaAdapter } from 'better-auth/adapters/prisma';
import { prisma } from '@/lib/prisma';
export const auth = betterAuth({
appName: 'Rewind',
database: prismaAdapter(prisma, { provider: 'postgresql' }),
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
},
});
L’adaptateur écrit dans le schéma que vous possédez déjà, si bien que les utilisateurs, les sessions et les comptes sont des tables ordinaires que vous pouvez joindre. Ce seul détail supprime la friction principale des services hébergés, où chaque requête concernant un utilisateur traverse une frontière réseau qui vous échappe.
Le client est déduit du serveur
La partie navigateur se crée avec createAuthClient, et elle est volontairement mince. Ce qui la rend intéressante, c’est le paramètre de type : passer l’instance serveur en type donne au client la connaissance complète de ce que le serveur accepte, y compris ce qu’un plugin a ajouté :
import { createAuthClient } from 'better-auth/react';
import { twoFactorClient } from 'better-auth/client/plugins';
import type { auth } from '@/lib/auth';
export const authClient = createAuthClient({
plugins: [twoFactorClient()],
});
export const { signIn, signUp, signOut, useSession } = authClient;
Renommez un champ dans la configuration serveur et l’appel cesse de compiler, ce qui est exactement le retour que l’on attend d’une couche d’authentification. Le hook de session renvoie un utilisateur typé plutôt qu’un sac de propriétés inconnues, donc la forme lue dans un composant est celle que votre base stocke réellement.
Des plugins plutôt qu’un monolithe
Le cœur gère les utilisateurs, les sessions et les comptes, et tout le reste s’active à la demande. C’est important, car la plupart des bibliothèques d’authentification livrent une surface unique où l’on paie la complexité de fonctionnalités jamais activées, alors qu’ici l’API ne grandit qu’avec ce que vous enregistrez :
import { admin, organization, twoFactor } from 'better-auth/plugins';
export const auth = betterAuth({
database: prismaAdapter(prisma, { provider: 'postgresql' }),
plugins: [
twoFactor({ issuer: 'Rewind' }),
organization({ allowUserToCreateOrganization: true }),
admin(),
],
});
Ajouter organization apporte le multi-tenant avec ses équipes, ses invitations et ses rôles, et étend au passage les types User et Session. Les clés d’accès, l’authentification unique, les sessions multiples et les abonnements Stripe suivent le même schéma, ce qui garde un petit produit petit et permet à un produit d’entreprise de grandir sans migration.
Des sessions lues côté serveur
L’accès serveur à la session passe par auth.api, la même instance que celle utilisée par les routes, donc sans SDK distinct ni aller-retour HTTP vers votre propre backend. L’envelopper dans le cache de React ramène une requête qui rend cinq composants imbriqués à une seule lecture :
import { cache } from 'react';
import { headers } from 'next/headers';
import { auth } from '@/lib/auth';
export const getSession = cache(async () => auth.api.getSession({ headers: await headers() }));
Comme la session vit dans votre base et non derrière l’API d’un tiers, une page coûteuse peut joindre les données utilisateur et les données applicatives en une requête au lieu de deux allers-retours. C’est toute la différence entre une authentification vue comme une dépendance et une authentification vue comme un service.
Ce que vous cessez de maintenir
La valeur tient moins à une fonctionnalité particulière qu’à la liste de ce qui cesse d’être votre problème. La limitation de débit sur les points sensibles, les drapeaux de cookies sécurisés, la protection CSRF, la liaison entre un compte mot de passe et un compte social, les jetons de vérification et de réinitialisation avec leur expiration : tout arrive configuré, et vous réglez au lieu de construire.
Le compromis est réel et mérite d’être dit. Vous possédez la base, donc vous possédez ses migrations et ses sauvegardes, et une panne sur vos tables d’authentification est une panne de votre produit. Un service hébergé absorbe ce poids opérationnel, ce qui reste une raison valable de payer quand personne dans l’équipe ne peut le porter.
Quand le choisir
Better Auth convient aux produits où la table utilisateur fait partie du domaine plutôt que d’être un détail externe, ce qui couvre la majorité des SaaS avec organisations, rôles ou facturation liés aux comptes. Il convient aussi aux équipes qui veulent une authentification relisible en revue de code plutôt que cliquée dans un tableau de bord. Le pont typé entre serveur et client est ce qui le rend agréable au quotidien, et le modèle de plugins est ce qui le garde viable à mesure que le produit grandit.