Ajout de l'authentification avec Better Auth dans React Router

Implémente l’auth complète avec Better Auth, Zod et Prisma : sessions sécurisées, rôles admin protégés et hooks React.

5 min read
Déverrouillez votre potentiel

avec React Router 7

Vous en avez marre de...

❌ perdre du temps à chercher des informations éparpillées
❌ ne pas avoir de retour sur votre progression
Assistant IA spécialisé

Posez vos questions 24/7 à notre IA experte en React Router 7

Quiz interactifs

Validez vos acquis avec des quiz personnalisés et un feedback instantané

9 modules
72 leçons
Accès à vie
299.49
-35%

Pourquoi intégrer Better Auth ?

Notre back-office admin est prêt, mais aucune authentification n’était encore en place. Dans cette leçon, nous allons utiliser Better Auth pour gérer :

  • email + mot de passe,
  • sessions cookie signées,
  • rôles customer, administrator, super_administrator.

Objectif : mettre en place une authentification complète, avec SSR, entièrement typée, et réutilisable sur toutes les routes protégées.

Mise à jour des dépendances

1. Installer Better Auth

Terminal
1
npm install better-auth

2. Exigence Zod ≥ 3 .25

Terminal
1
npm install zod@3.25.76

Pourquoi ? Better Auth vérifie les schémas côté serveur avec Zod. Sans la bonne version la commande npm install better-auth échoue (erreur vue dans la vidéo).


Variables d’environnement

.env
1
BETTER_AUTH_URL=http://localhost:5173 # même port que le dev-server
2
BETTER_AUTH_SECRET=super-secret-key-32-chars

BETTER_AUTH_SECRET vient de la doc Better Auth (copie / colle le token généré). • Garder ces clés hors du repo (.gitignore).


Adapter Prisma

Better Auth ajoute les tables : sessions, accounts, verification.

Terminal
1
npx prisma migrate dev --name add-authentication-with-betterauth

Le diff dans schema.prisma :

prisma/schema.prisma {85,96}
1
+model Session {
2
+ id String @id
3
+ token String @unique
4
+ userId String
5
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
6
+ expiresAt DateTime
7
+ createdAt DateTime
8
+ updatedAt DateTime
9
+}

Configurer le serveur Better Auth

app/server/auth.server.ts
1
import { betterAuth } from "better-auth";
2
import { prismaAdapter } from "better-auth/adapters/prisma";
3
import { admin } from "better-auth/plugins";
4
import { PrismaClient } from "~/../generated/prisma/client";
5
6
const prisma = new PrismaClient();
7
8
export const auth = betterAuth({
9
baseURL: process.env.BETTER_AUTH_URL,
10
secret: process.env.BETTER_AUTH_SECRET,
11
emailAndPassword: { enabled: true },
12
database: prismaAdapter(prisma, { provider: "postgresql" }),
13
plugins: [admin({ adminRoles: ["administrator", "super_administrator"] })],
14
});

Explications :

  1. prismaAdapter branche Better Auth sur la DB existante.
  2. admin() déclare les rôles qui auront accès au back-office.
  3. emailAndPassword.enabled = true active la connexion classique.

Route API pour Better Auth

app/routes/api.auth.$.ts
1
import { auth } from "~/server/auth.server";
2
3
export async function loader({ request }: LoaderFunctionArgs) {
4
return auth.handler(request);
5
}
6
export async function action({ request }: ActionFunctionArgs) {
7
return auth.handler(request);
8
}

Cette « resource route » reçoit toutes les requêtes du SDK : signIn, signUp, signOut…


Client React Better Auth

app/lib/auth-client.ts
1
import { adminClient } from "better-auth/client/plugins";
2
import { createAuthClient } from "better-auth/react";
3
4
export const authClient = createAuthClient({
5
baseURL: "http://localhost:5173", // même que .env
6
plugins: [adminClient()],
7
});
8
9
export const { signIn, signUp, signOut, useSession } = authClient;

Le client gère :

  • requête vers /api/auth ;
  • stockage du cookie de session better-auth.session_token.

Pages Login & Register

Validation Zod × Conform

utils/auth-schemas.ts
1
import { z } from "zod";
2
3
export const RegisterSchema = z
4
.object({
5
name: z.string().min(2),
6
email: z.string().email(),
7
password: z.string().min(8),
8
confirmPassword: z.string(),
9
})
10
.refine((d) => d.password === d.confirmPassword, {
11
path: ["confirmPassword"],
12
message: "Les mots de passe ne correspondent pas",
13
});
14
15
export const LoginSchema = z.object({
16
email: z.string().email(),
17
password: z.string().min(1),
18
});

Formulaire Register (extrait)

app/routes/_public+/register.tsx
1
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
2
import { parseWithZod } from "@conform-to/zod";
3
import { signUp } from "~/lib/auth-client";
4
import { RegisterSchema } from "~/utils/auth-schemas";
5
6
const [form, fields] = useForm({
7
onValidate({ formData }) {
8
return parseWithZod(formData, { schema: RegisterSchema });
9
},
10
});
11
12
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
13
e.preventDefault();
14
const formData = new FormData(e.currentTarget);
15
const submission = parseWithZod(formData, { schema: RegisterSchema });
16
if (submission.status !== "success") return;
17
18
const { error } = await signUp.email(submission.value);
19
if (!error) navigate("/");
20
}

Aucune action serveur : le SDK Better Auth gère la session et le cookie.


Helpers d’accès serveur

Obtenir l’utilisateur (optionnel)

app/server/auth.server.ts
1
export async function getOptionalUser(request: Request) {
2
try {
3
const session = await auth.api.getSession({ headers: request.headers });
4
return session?.user ?? null;
5
} catch {
6
return null;
7
}
8
}

Exiger un utilisateur connecté

app/server/auth.server.ts
1
import { redirect } from "react-router";
2
3
export async function requireUser(request: Request) {
4
const user = await getOptionalUser(request);
5
if (!user) {
6
const url = new URL(request.url);
7
throw redirect(`/login?redirectTo=${url.pathname}`);
8
}
9
return user; // utilisateur typé
10
}

Exiger un admin

app/server/auth.server.ts
1
export async function requireAdmin(request: Request) {
2
const user = await requireUser(request);
3
if (!["administrator", "super_administrator"].includes(user.role)) {
4
await auth.api.signOut({ headers: request.headers }); // purge cookie
5
throw redirect("/login");
6
}
7
return user;
8
}

Hook global : useOptionalUser()

app/root.tsx
1
export async function loader({ request }: Route.LoaderArgs) {
2
const user = await getOptionalUser(request);
3
return data({ user });
4
}
5
6
export function useOptionalUser() {
7
return useRouteLoaderData<typeof loader>("root")?.user ?? null;
8
}

– Appelez-le dans la Navbar pour afficher « Bonjour Alice » ou un bouton « Login ».


Protection des routes admin

app/routes/admin+/_adminLayout.tsx
1
export async function loader({ request }: Route.LoaderArgs) {
2
await requireAdmin(request);
3
return data(null);
4
}

Un invité ➜ redirect /login?redirectTo=/admin.


Déconnexion sécurisée

app/components/layout/navbar.tsx
1
<button
2
onClick={() => signOut().then(() => navigate("/"))}
3
className="text-sm text-gray-600 hover:text-black"
4
>
5
Déconnexion
6
</button>

Le SDK supprime le cookie, le loader root ne renvoie plus d’utilisateur.


Résultat

  • Auth complet (inscription, connexion, déconnexion).
  • Sessions HTTP-only signées ; cookie nettoyé en cas d’erreur.
  • Rôles admin protégés côté serveur (requireAdmin).
  • SSR : les loaders savent si l’utilisateur est connecté avant le rendu.
  • Pages Login / Register validées client et serveur (Zod + Conform).

Ton back-office admin est désormais inaccessible aux visiteurs et prêt pour la suite du projet.

Premium
Quiz interactif
Testez vos connaissances et validez votre compréhension du module avec notre quiz interactif.
1

Comprendre les concepts fondamentaux

Quelle est la principale différence entre les composants client et serveur dans React ?

Les composants client s'exécutent uniquement dans le navigateur
Les composants serveur peuvent utiliser useState
Les composants client sont plus rapides
Il n'y a aucune différence significative
2

Optimisation des performances

Quelle technique est recommandée pour éviter les rendus inutiles dans React ?

Utiliser React.memo pour les composants fonctionnels
Ajouter plus d'états locaux
Éviter d'utiliser les props
Toujours utiliser les class components
3

Architecture des données

Quel hook permet de gérer les effets de bord dans un composant React ?

useEffect
useState
useMemo
useContext
4

Gestion des erreurs

Comment implémenter la gestion des erreurs pour les requêtes API dans React ?

Utiliser try/catch avec async/await
Ignorer les erreurs
Toujours afficher un message d'erreur
Rediriger l'utilisateur
5

Déploiement et CI/CD

Quelle est la meilleure pratique pour déployer une application React en production ?

Utiliser un service CI/CD comme GitHub Actions
Copier les fichiers manuellement via FTP
Envoyer le code source complet
Ne jamais mettre à jour l'application

Débloquez ce quiz et tous les autres contenus premium en achetant ce cours