Apprends à implémenter un flow sécurisé de réinitialisation de mot de passe dans React Router 7 avec Prisma, Conform et Zod. Guide complet et concret.
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é
Dès que tu imposes un couple e-mail / mot de passe, tu dois prévoir l’oubli. Le flux idéal :
Voyons comment produire ce flow dans React Router 7 avec Prisma, Conform et Zod.
1<Form …>2{/* …inputs e-mail / mdp… */}3<div className="flex justify-between">4<Link to="/register" className="text-xs text-sky-700">S’inscrire</Link>5<Link to="/forgot-password" className="text-xs text-gray-400">6Mot de passe oublié ?7</Link>8</div>9</Form>
Ce simple Link
pointe vers une route dédiée. Créons-la.
/forgot-password
Si l’utilisateur est déjà loggé, inutile de lui proposer le reset :
1export async function loader({ request }: LoaderArgs) {2const user = await getOptionalUser({ request })3if (user) throw redirect("/") // déjà connecté4return null5}
1import { z } from "zod"23export const ForgotPasswordSchema = z.object({4email: z.string().email("Adresse invalide"),5})
1export async function triggerResetPasswordRequest({ email }: { email: string }) {2// 1 - le compte existe-t-il ?3const user = await prisma.user.findUnique({ where: { email } })4if (!user) return // pas d’info : pas de faille UX56// 2 - générer un token + date d’expiration7const token = crypto.randomUUID().slice(0, 32)8const expiresAt = new Date(Date.now() + 1000 * 60 * 30) // 30 min910await prisma.user.update({11where: { id: user.id },12data: { resetToken: token, resetTokenExpiresAt: expiresAt },13})1415// 3 - construire le lien16const link = href("/reset-password") + `?token=${token}`17console.log({ link }) // @todo : envoyer un e-mail réel18}
Le modèle Prisma se dote donc de deux colonnes facultatives :
1resetToken String? @unique2resetTokenExpiresAt DateTime?
Small hack : on logue le lien au lieu d’envoyer un e-mail – parfait pour le dev local.
1<Form method="POST" …>2<Field … name="email" … />3{actionData?.message && (4<p className="text-center text-sm text-gray-500">5{actionData.message}6</p>7)}8<button className="btn-primary w-full">Réinitialiser le mot de passe</button>9</Form>
Quel que soit l’e-mail, la réponse est la même : « Si un compte est associé, tu recevras un lien… » → on ne révèle rien aux robots malveillants.
/reset-password
1export async function loader({ request }: LoaderArgs) {2const token = new URL(request.url).searchParams.get("token") ?? ""3const user = await prisma.user.findUnique({4where: { resetToken: token },5select: { id: true, resetTokenExpiresAt: true },6})78const isValid =9user && user.resetTokenExpiresAt && user.resetTokenExpiresAt > new Date()1011return data({ isValid, token }) // token renvoyé pour l’action12}
1{isValid ? (2<Form method="POST" …>3<Field … name="password" />4<Field … name="passwordConfirmation" />5<button className="btn-primary w-full">6Réinitialiser le mot de passe7</button>8</Form>9) : (10<p className="text-gray-500 text-center">11Lien invalide ou expiré.12</p>13)}
1export const ResetPasswordSchema = z2.object({3password: z.string().min(6),4passwordConfirmation: z.string(),5})6.refine((d) => d.password === d.passwordConfirmation, {7message: "Les mots de passe ne correspondent pas",8path: ["passwordConfirmation"],9})
1export async function resetUserPassword({2token, password,3}: { token: string; password: string }) {4const user = await prisma.user.findUnique({ where: { resetToken: token } })5if (6!user ||7!user.resetTokenExpiresAt ||8user.resetTokenExpiresAt < new Date()9) throw new Error("Token expiré")1011await prisma.user.update({12where: { id: user.id },13data: {14password: await hashPassword({ password }),15resetToken: null,16resetTokenExpiresAt: null,17},18})19}
Le token est à usage unique : seconde tentative => message d’expiration.
/login
pour gagner un clic.superRefine
de l’action reteste le token
au cas où il aurait expiré pendant que l’utilisateur tapait le formulaire./forgot-password
déclenche l’envoi./reset-password?token=…
gère le changement.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 ?