Authentification
Ce guide présente le système d’authentification complet du projet, incluant l’inscription, la connexion, la gestion des sessions et la protection des routes.
Présentation
Le projet utilise un système d’authentification moderne et sécurisé basé sur :
- JWT (JSON Web Tokens) pour les sessions stateless avec la librairie
jose - Bcrypt pour le hachage sécurisé des mots de passe (10 rounds)
- Cookies HTTP-only pour stocker les sessions de manière sécurisée
- Server Actions Next.js 15 pour la soumission des formulaires
- MongoDB via
@workspace/databasepour la persistance des utilisateurs
Architecture
Composants principaux
-
Librairie d’authentification (
apps/web/lib/auth.ts)- Gestion des sessions JWT
- Hachage et vérification des mots de passe
- Gestion des cookies
-
Pages d’authentification
/inscription- Création de compte/connexion- Connexion utilisateur
-
Pages protégées
/utilisateur- Liste des utilisateurs/utilisateur/[id]- Détail d’un utilisateur
Structure des dossiers
apps/web/app/
└── utilisateur/
├── page.tsx # Liste des utilisateurs
└── [id]/
└── page.tsx # Détail d'un utilisateurPages Utilisateurs
Liste des utilisateurs (/utilisateur)
Cette page affiche la liste de tous les utilisateurs enregistrés dans l’application.
Fonctionnalités :
- Affichage sous forme de table
- Informations : nom, email, date de création
- Lien vers la page de détail de chaque utilisateur
- Bouton pour créer un nouvel utilisateur
Exemple de code :
import { listUsers } from "@workspace/database";
export default async function UtilisateursPage() {
const users = await listUsers();
return (
<Table>
{users.map((user) => (
<TableRow key={user._id}>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
</TableRow>
))}
</Table>
);
}Gestion des sessions JWT
import { createSession, getSession, deleteSession } from "@/lib/auth";
// Créer une session (après inscription ou connexion)
await createSession(userId, email, name);
// Récupérer la session courante
const session = await getSession();
// session = { userId, email, name, expiresAt } ou null
// Supprimer la session (déconnexion)
await deleteSession();Détails techniques :
- Les JWT sont signés avec HS256
- Durée de validité : 7 jours
- Cookie HTTP-only, Secure en production, SameSite=lax
- Le cookie est nommé
session
Gestion des mots de passe
import { hashPassword, verifyPassword } from "@/lib/auth";
// Hasher un mot de passe (avant stockage)
const hashedPassword = await hashPassword("mon-mot-de-passe");
// Utilise bcrypt avec 10 rounds de salt
// Vérifier un mot de passe (lors de la connexion)
const isValid = await verifyPassword("mon-mot-de-passe", hashedPassword);
// Retourne true si le mot de passe correspondBonnes pratiques :
- Ne jamais stocker de mot de passe en clair
- Toujours hasher avant d’appeler
createUser() - Utiliser
verifyPassword()pour comparer les mots de passe
Pages d’authentification
Page d’inscription (/inscription)
Permet aux utilisateurs de créer un nouveau compte.
Fonctionnalités :
- Validation des champs (nom, email, mot de passe)
- Vérification de la longueur du mot de passe (minimum 6 caractères)
- Confirmation du mot de passe
- Vérification de l’unicité de l’email
- Hachage automatique du mot de passe
- Création de session automatique après inscription
Exemple de code :
// apps/web/app/inscription/page.tsx
async function registerAction(formData: FormData) {
"use server";
const name = formData.get("name") as string;
const email = formData.get("email") as string;
const password = formData.get("password") as string;
// VĂ©rifier si l'email existe dĂ©jĂ
const existingUser = await findUserByEmail(email);
if (existingUser) {
redirect("/inscription?error=Email%20d%C3%A9j%C3%A0%20utilis%C3%A9");
}
// Hasher le mot de passe
const hashedPassword = await hashPassword(password);
// Créer l'utilisateur
const user = await createUser({ name, email, password: hashedPassword });
// Créer la session
await createSession(user._id.toString(), user.email, user.name);
// Rediriger
redirect("/utilisateur");
}Page de connexion (/connexion)
Permet aux utilisateurs de se connecter avec leur compte existant.
Fonctionnalités :
- Validation des champs (email, mot de passe)
- Recherche de l’utilisateur par email
- Vérification du mot de passe avec bcrypt
- Création de session en cas de succès
- Messages d’erreur explicites
Exemple de code :
// apps/web/app/connexion/page.tsx
async function loginAction(formData: FormData) {
"use server";
const email = formData.get("email") as string;
const password = formData.get("password") as string;
// Trouver l'utilisateur
const user = await findUserByEmail(email);
if (!user || !user.password) {
redirect("/connexion?error=Identifiants%20invalides");
}
// Vérifier le mot de passe
const isValid = await verifyPassword(password, user.password);
if (!isValid) {
redirect("/connexion?error=Identifiants%20invalides");
}
// Créer la session
await createSession(user._id.toString(), user.email, user.name);
// Rediriger
redirect("/utilisateur");
}Package @workspace/database
Les données utilisateurs sont gérées par le package @workspace/database qui fournit :
// Fonctions disponibles
import {
createUser, // Créer un utilisateur
findUserById, // Trouver par ID
findUserByEmail, // Trouver par email
listUsers, // Lister tous les utilisateurs
updateUser, // Mettre Ă jour
deleteUser, // Supprimer
countUsers, // Compter
} from "@workspace/database";Interface User
interface User {
_id?: ObjectId;
email: string;
name: string;
password?: string;
createdAt: Date;
updatedAt: Date;
}Pages Utilisateurs
Liste des utilisateurs (/utilisateur)
Cette page affiche la liste de tous les utilisateurs enregistrés dans l’application.
Fonctionnalités :
- Affichage sous forme de table
- Informations : nom, email, date de création
- Lien vers la page de détail de chaque utilisateur
- Bouton pour créer un nouvel utilisateur
Exemple de code :
import { listUsers } from "@workspace/database";
export default async function UtilisateursPage() {
const users = await listUsers();
return (
<Table>
{users.map((user) => (
<TableRow key={user._id.toString()}>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
</TableRow>
))}
</Table>
);
}Page détail utilisateur (/utilisateur/[id])
Cette page affiche les informations détaillées d’un utilisateur spécifique.
Fonctionnalités :
- Affichage du nom et email
- Dates de création et de mise à jour
- Actions : modifier et supprimer (à implémenter)
Concepts Next.js utilisés :
- Routes dynamiques avec
[id] generateMetadatapour le SEO dynamiquenotFound()pour les utilisateurs inexistants
Protection des routes
Pour protéger une route et exiger une authentification, utilisez getSession() :
import { getSession } from "@/lib/auth";
import { redirect } from "next/navigation";
export default async function ProtectedPage() {
// Vérifier la session
const session = await getSession();
// Rediriger si non connecté
if (!session) {
redirect("/connexion");
}
// Page accessible uniquement aux utilisateurs connectés
return (
<div>
<h1>Bienvenue {session.name}</h1>
<p>Email: {session.email}</p>
</div>
);
}Middleware (optionnel)
Pour protéger plusieurs routes à la fois, créez un middleware :
// apps/web/middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { getSession } from "@/lib/auth";
export async function middleware(request: NextRequest) {
const session = await getSession();
// Routes protégées
const protectedRoutes = ["/utilisateur", "/profil"];
const isProtectedRoute = protectedRoutes.some(route =>
request.nextUrl.pathname.startsWith(route)
);
// Rediriger si non authentifié
if (isProtectedRoute && !session) {
return NextResponse.redirect(new URL("/connexion", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};Configuration
Variables d’environnement
Créez un fichier .env.local à la racine de l’application web (apps/web/.env.local) :
# Configuration MongoDB
MONGODB_URI=mongodb://localhost:27017
MONGODB_DB_NAME=tp-nextjs
# Secret JWT (IMPORTANT : changez cette valeur en production)
JWT_SECRET=votre-secret-jwt-tres-securise-et-aleatoire
# URLs de l'application
NEXT_PUBLIC_API_URL=http://localhost:3000
NEXT_PUBLIC_DOC_URL=http://localhost:3001Dépendances
Le système d’authentification utilise les packages suivants :
jose(^6.0.11) : Gestion des JWT (création, vérification, signature)bcryptjs(^3.0.2) : Hachage des mots de passe avec saltmongodb(^6.12.0) : Base de données pour stocker les utilisateurs@workspace/database: Package interne pour les opérations CRUD
Librairie d’authentification
Le fichier apps/web/lib/auth.ts expose les fonctions suivantes :
Sécurité
Bonnes pratiques implémentées
- Cookies HTTP-only : les tokens JWT sont stockés dans des cookies HTTP-only, inaccessibles au JavaScript côté client
- Cookies Secure : en production, les cookies utilisent l’attribut Secure (HTTPS uniquement)
- SameSite=lax : protection contre les attaques CSRF
- Hachage bcrypt : 10 rounds de salt pour un bon équilibre sécurité/performance
- Normalisation des emails : conversion en minuscules pour éviter les doublons
- Index unique : empêche la création de comptes avec le même email
- Validation des entrées : vérification côté serveur avec Server Actions
- Messages d’erreur génériques : “Identifiants invalides” pour ne pas révéler si l’email existe
Points d’amélioration possibles
- Rate limiting : limiter le nombre de tentatives de connexion
- Vérification d’email : envoyer un email de confirmation à l’inscription
- Récupération de mot de passe : flux de réinitialisation par email
- Authentification à deux facteurs : ajouter une couche de sécurité supplémentaire
- Sessions multiples : permettre plusieurs sessions actives par utilisateur
- Révocation de tokens : blacklist de tokens révoqués (nécessite Redis ou équivalent)
Débogage
Vérifier la connexion MongoDB
# Vérifier que MongoDB est démarré
docker-compose ps
# Voir les logs MongoDB
docker-compose logs -f mongodb
# Accéder au shell MongoDB
docker exec -it tp-nextjs-mongodb mongosh
# Dans le shell, vérifier la base de données
use tp-nextjs
db.users.find()Problèmes courants
Erreur : “MongoDB non disponible”
- Solution : Vérifiez que Docker est démarré et lancez
docker-compose up -d
Erreur : “Email déjà utilisé”
- Solution : L’email existe déjà dans la base de données. Utilisez un autre email ou supprimez l’utilisateur existant.
Erreur : “JWT malformed”
- Solution : Le cookie de session est corrompu. Supprimez le cookie
sessiondans les DevTools du navigateur.