Créer et sécuriser les comptes utilisateurs
Définissez le modèle utilisateur, l’authentification et la validation des mots de passe.
Créer et sécuriser les comptes utilisateurs
Dans cette leçon, tu vas apprendre à :
- Définir et personnaliser le modèle User dans Strapi 5
- Construire des formulaires d’inscription et de connexion avec Remix
- Mettre en place l’authentification par JWT et la sécurisation de routes
- Ajouter un endpoint personnalisé pour comparer des mots de passe
Ton Algomax : concret, passionné, tutoiement bienveillant.
1 Définir le modèle User dans Strapi 5
Commence par créer un Collection Type user dans Strapi. Voici la configuration de base, avec les champs clés pour la sécurité :
1{2"kind": "collectionType",3"collectionName": "up_users",4"info": {5"displayName": "User",6"singularName": "user",7"pluralName": "users"8},9"options": {10"timestamps": true11},12"attributes": {13"username": {14"type": "string",15"minLength": 3,16"unique": true,17"required": true18},19"email": {20"type": "email",21"minLength": 6,22"required": true23},24"password": {25"type": "password",26"minLength": 6,27"private": true28},29"confirmed": {30"type": "boolean",31"default": false32},33"blocked": {34"type": "boolean",35"default": false36},37"role": {38"type": "relation",39"relation": "manyToOne",40"target": "plugin::users-permissions.role"41}42}43}
Tip
On utilise le type password intégré de Strapi. Il hache le mot de passe avant la
sauvegarde et ne l’expose pas via l’API (private: true).
2 Ajouter un contrôleur custom pour comparer les mots de passe
Pour vérifier dynamiquement si le mot de passe actuel d’un utilisateur est valide (p. ex. pour changer son mot de passe), crée un endpoint comparePasswords :
1import { factories } from "@strapi/strapi";23export default factories.createCoreController(4"plugin::users-permissions.user",5({ strapi }) => ({6async comparePasswords(ctx) {7const { email, currentPassword } = ctx.request.body;8if (!email) {9throw new Error("We need an email to use this API");10}11if (!currentPassword || currentPassword.length < 6) {12throw new Error("Password must have at least 6 characters");13}1415const user = await strapi.db16.query("plugin::users-permissions.user")17.findOne({ where: { email } });1819const valid = await strapi20.plugin("users-permissions")21.service("user")22.validatePassword(currentPassword, user?.password);2324return { isPasswordValid: valid };25},26})27);
Pourquoi ce custom ?
Tu pourras appeler /compare-passwords depuis le frontend pour vérifier
l’ancien mot de passe avant de le mettre à jour.
3 Intégrer l’inscription et la connexion dans Remix
3.1 Route d’inscription (signup)
1import { Form, useActionData, redirect } from "remix";2import { z } from "zod";3import { createUser } from "~/utils/user.server";45export const action = async ({ request }) => {6const form = await request.formData();7const data = {8username: form.get("username"),9email: form.get("email"),10password: form.get("password"),11};12// Valide avec Zod13const schema = z.object({14username: z.string().min(3),15email: z.string().email(),16password: z.string().min(6),17});18const result = schema.safeParse(data);19if (!result.success) {20return { errors: result.error.flatten().fieldErrors };21}22// Appelle Strapi23await createUser(result.data);24return redirect("/login");25};2627export default function Signup() {28const actionData = useActionData();29return (30<Form method="post">31<input name="username" placeholder="Username" />32<input name="email" type="email" placeholder="Email" />33<input name="password" type="password" placeholder="Password" />34<button type="submit">Inscription</button>35{actionData?.errors && <div>{JSON.stringify(actionData.errors)}</div>}36</Form>37);38}
1import { fetchAPI } from "./fetchAPI";23export async function createUser(data: {4username: string;5email: string;6password: string;7}) {8return fetchAPI("/api/auth/local/register", {9method: "POST",10body: JSON.stringify(data),11});12}
Tip
On utilise fetchAPI pour centraliser l’appel à Strapi.
3.2 Route de connexion (login)
1import { Form, useActionData, redirect } from "remix";2import { loginUser } from "~/utils/user.server";34export const action = async ({ request }) => {5const form = await request.formData();6const identifier = form.get("email");7const password = form.get("password");8try {9const { jwt } = await loginUser({ identifier, password });10// Stocker le JWT dans un cookie sécurisé11return redirect("/", {12headers: {13"Set-Cookie": `jwt=${jwt}; HttpOnly; Path=/; Max-Age=2592000`,14},15});16} catch {17return { error: "Identifiants incorrects" };18}19};2021export default function Login() {22const actionData = useActionData();23return (24<Form method="post">25<input name="email" type="email" placeholder="Email" />26<input name="password" type="password" placeholder="Password" />27<button type="submit">Connexion</button>28{actionData?.error && <div>{actionData.error}</div>}29</Form>30);31}
4 Protéger les routes avec Remix loaders
Pour sécuriser une page (profil, panier…), on vérifie le JWT dans le loader :
1import { parse } from "cookie";2import { getUserByJwt } from "./user.server";34export async function requireUser(request: Request) {5const cookie = request.headers.get("Cookie") || "";6const { jwt } = parse(cookie);7if (!jwt) throw redirect("/login");8const user = await getUserByJwt(jwt);9if (!user) throw redirect("/login");10return user;11}
1import { LoaderFunction } from "remix";2import { requireUser } from "~/utils/session.server";34export const loader: LoaderFunction = async ({ request }) => {5const user = await requireUser(request);6return user;7};89export default function Profile({ data }) {10return (11<div>12<h2>Bienvenue {data.username}</h2>13<p>Email : {data.email}</p>14</div>15);16}
Route sécurisée
Si le JWT est invalide ou expiré, on redirige systématiquement vers /login.
5 Bonnes pratiques et points clés
- Toujours hash et salter les mots de passe (Strapi le gère automatiquement).
- Utiliser HttpOnly cookies pour stocker le JWT et le rendre inaccessible au JS.
- Valider les inputs avec Zod ou une autre librairie de validation.
- Personnaliser les contrôleurs Strapi pour ajouter des endpoints (ex.
comparaison de mot de passe). - Protéger systématiquement les loaders dans Remix avec une fonction
requireUser.
Exercices rapides
-
Ajouter la confirmation par email
- Dans Strapi, active le plugin Users & Permissions et configure le
email-confirmation. - Ajoute le champ
confirmationTokendans le modèle et gère la route
/auth/confirm-email.
- Dans Strapi, active le plugin Users & Permissions et configure le
-
Implémenter la réinitialisation de mot de passe
- Crée un controller Strapi pour générer un
resetPasswordToken. - Conçois un formulaire Remix pour saisir l’email, recevoir le lien, puis
choisir un nouveau mot de passe.
- Crée un controller Strapi pour générer un
-
Renouvellement automatique du JWT
- Avant chaque requête, vérifie la date d’expiration du token en front.
- Si proche de l’expiration, appelle un endpoint
/refresh-tokendans Strapi
(à créer) et mets à jour le cookie.
Liens utiles :