Apprends à sécuriser tes routes dans React Router 7 en gérant l’authentification côté serveur et en évitant les failles de sécurité.
avec React Router 7
Posez vos questions 24/7 à notre IA experte en React Router 7
Validez vos acquis avec des quiz personnalisés et un feedback instantané
Toutes les pages ne se valent pas :
- un tableau de bord, - un formulaire de paiement, - des paramètres de compte ...
Ces écrans doivent être accessibles uniquement aux utilisateurs authentifiés.
Dans React Router 7 tu peux tester la session directement dans chaque route ;
mais recopier le même if (!user) redirect(...)
devient vite pénible et risqué :
la première omission est une faille de sécurité.
Dans cette leçon tu vas :
loader
que dans les action
;Le module précédent a posé le cookie __session
avec l’userId
à la connexion.
Créons un utilitaire pour le récupérer :
1import { createCookieSessionStorage } from "react-router"2import { prisma } from "./db.server"34type SessionData = { userId: string }56const { getSession, commitSession, destroySession } =7createCookieSessionStorage<SessionData>({8cookie: {9name: "__session",10sameSite: "lax",11httpOnly: true,12path: "/",13secrets: [process.env.SESSION_SECRET!], // ➜ prod only14secure: process.env.NODE_ENV === "production",15},16})1718export async function getOptionalUser({ request }: { request: Request }) {19const session = await getSession(request.headers.get("Cookie"))20const id = session.get("userId") as string | undefined21if (!id) return null2223// vérif anti-forgery : l’id existe *vraiment* en base24return prisma.user.findUnique({25where: { id: Number(id) },26select: { id: true, firstName: true, lastName: true },27})28}
getOptionalUser
renvoie soit l’utilisateur, soit null
.
C’est idéal pour afficher un bouton Login lorsque personne n’est connecté.
requireUser
: la garde de sécuritéLe profil, la facturation, l’administration… doivent être fermés : pas d’utilisateur ⇒ pas d’accès. On factorise ce comportement dans une seule fonction :
1import { redirect } from "react-router"23export async function requireUser({ request }: { request: Request }) {4const user = await getOptionalUser({ request })5if (!user) {6throw redirect("/login") // ⬅️ stoppe le loader et redirige7}8return user9}
Remarque :
redirect()
lève déjà une Response
HTTP 302, React Router capture
l’exception et envoie le bon header au navigateur – aucun composant
d’erreur n’est rendu.
1import type { LoaderFunctionArgs } from "react-router"2import { requireUser } from "~/server/sessions.server"34export async function loader({ request }: LoaderFunctionArgs) {5// 🔒 bloque d’emblée les visiteurs6const user = await requireUser({ request })7return { user }8}
Visiteur anonyme ? Il est téléporté sur /login
.
Utilisateur connecté ? Le loader
continue et fournit le profil.
Les mutations POST/PUT/DELETE doivent aussi être protégées :
1import type { ActionFunctionArgs } from "react-router"23export async function action({ request }: ActionFunctionArgs) {4const user = await requireUser({ request }) // 🔒 encore5const form = await request.formData()6// … mise à jour du profil …7return null8}
Sans ce garde-fou un utilisateur malveillant pourrait envoyer une requête
curl -X POST /profile
et modifier n’importe quelle donnée.
La racine de l’app (app/root.tsx
) charge toujours ;
profitons-en pour y remplir l’objet utilisateur une seule fois :
1export async function loader({ request }: LoaderFunctionArgs) {2const user = await getOptionalUser({ request })3return data({ user })4}
Puis expose-le via un hook maison :
1import { useRouteLoaderData } from "react-router"23export function useOptionalUser() {4const data = useRouteLoaderData<typeof loader>("root")5return data?.user ?? null6}
Maintenant n’importe quelle page fait simplement :
1const user = useOptionalUser()
sans requête supplémentaire.
1sequenceDiagram2participant B as Navigateur3participant SSR as Loader4participant DB as DB Prisma56B->>SSR: GET /profile (cookies?)7SSR->>SSR: requireUser()8SSR->>DB: SELECT user WHERE id = cookie9alt user trouvé10SSR-->>B: 200 + HTML11else pas de user12SSR-->>B: 302 Redirect /login13end
Cookie
.requireUser
lit le cookie, teste l’existence en BDD.throw redirect("/login")
, aucune autre ligne ne s’exécute.getOptionalUser
lit l’id en cookie et vérifie qu’il existe en base.requireUser
encapsule la redirection ; on l’appelle dans chaque loader
et action à sécuriser.throw redirect()
– pas besoin de return
.root.tsx
; expose-le avec
useOptionalUser()
pour éviter les requêtes redondantes.Quelle est la principale différence entre les composants client et serveur dans React ?
Quelle technique est recommandée pour éviter les rendus inutiles dans React ?
Quel hook permet de gérer les effets de bord dans un composant React ?
Comment implémenter la gestion des erreurs pour les requêtes API dans React ?
Quelle est la meilleure pratique pour déployer une application React en production ?