Définissez le modèle utilisateur, l’authentification et la validation des mots de passe.
Dans cette leçon, tu vas apprendre à :
Ton Algomax : concret, passionné, tutoiement bienveillant.
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}
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
).
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);
Tu pourras appeler /compare-passwords
depuis le frontend pour vérifier
l’ancien mot de passe avant de le mettre à jour.
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}
On utilise fetchAPI
pour centraliser l’appel à Strapi.
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}
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}
Si le JWT est invalide ou expiré, on redirige systématiquement vers /login
.
requireUser
.Ajouter la confirmation par email
email-confirmation
.confirmationToken
dans le modèle et gère la route/auth/confirm-email
.Implémenter la réinitialisation de mot de passe
resetPasswordToken
.Renouvellement automatique du JWT
/refresh-token
dans StrapiLiens utiles :