Architecture de Sécurité & Authentification
Ce document présente l’architecture de sécurité et d’authentification mise en place sur le backend NestJS (apps/api). Le système repose sur JSON Web Tokens (JWT) via Passport.js (passport-jwt, @nestjs/jwt) pour la sécurisation des sessions et la bibliothèque bcrypt pour le hachage sécurisé des mots de passe.
1. Vue d’Ensemble du Flux de Sécurité
La sécurité repose sur deux piliers majeurs :
- L’Authentification (Qui êtes-vous ?) : Gérée par la vérification d’un token JWT signé transmis dans les en-têtes de requêtes HTTP sous la forme d’un jeton
Bearer. - L’Autorisation (Qu’avez-vous le droit de faire ?) : Gérée par un contrôle d’accès basé sur les rôles (RBAC - Role-Based Access Control) à l’aide d’un Guard NestJS vérifiant le rôle de l’utilisateur contre l’enum
UserRole(BUYER,SELLER,ADMIN).
2. Inscription et Hachage des Mots de Passe (POST /auth/register)
Le flux d’inscription permet de créer un nouvel utilisateur en base de données tout en garantissant la sécurité de ses identifiants.
[!IMPORTANT] Rôle choisi à l’inscription :
RegisterDto.roleaccepte'BUYER'ou'SELLER'—'ADMIN'(ou toute autre valeur) retombe silencieusement surBUYER. UnSELLERauto-inscrit est créé avecvendorStatus: PENDINGet ne peut pas encore publier de produit : voir Validation des comptes vendeur pour le détail complet de ce flux (champs métier requis, queue de validation admin, garde-fou surPOST /products).
Détails du Hachage (Bcrypt)
Le mot de passe en clair envoyé par l’utilisateur n’est jamais stocké tel quel en base de données.
- Fonction de hachage :
bcrypt.hash(password, saltRounds) - Facteur de coût (Salt Rounds) : 10. Cela représente un équilibre optimal entre sécurité contre les attaques par force brute (déchiffrement) et performances du serveur NestJS lors de la création de compte.
- Code associé (dans auth.service.ts) :
const hashedPassword = await bcrypt.hash(dto.password, 10);
3. Connexion et Génération du Token JWT (POST /auth/login)
Le flux de connexion vérifie l’identité de l’utilisateur et délivre un jeton JWT en cas de succès.
Structure du Payload JWT
Le jeton généré par le backend contient un payload minimal mais suffisant pour identifier l’utilisateur et ses droits à chaque requête :
{
"sub": "id-de-l-utilisateur-uuid",
"email": "user@example.com",
"role": "SELLER"
}sub(subject) : Contient l’ID unique de l’utilisateur (UUID).email: L’adresse e-mail associée.role: Le rôle de l’utilisateur, essentiel pour le routage de sécurité.- Durée de validité : Configurée à 1 jour (
1d) dans le auth.module.ts.
4. Sécurisation des Routes : Guards et Décorateurs
Pour protéger les endpoints de l’API, nous combinons trois éléments clés :
JwtAuthGuard: Valide le jeton d’accès présent dans l’en-têteAuthorization: Bearer <token>.RolesGuard: Vérifie si le rôle de l’utilisateur authentifié correspond aux rôles autorisés pour la route.- Décorateur
@Roles(...): Permet de définir les métadonnées de rôles nécessaires sur une route ou un contrôleur complet.
Fonctionnement Interne du JwtAuthGuard & JwtStrategy
Lorsque le JwtAuthGuard intercepte une requête :
- Il extrait le token via la méthode
ExtractJwt.fromAuthHeaderAsBearerToken(). - La stratégie jwt.strategy.ts valide la signature du token à l’aide de la clé secrète
JWT_SECRET. - Si le token est valide, la méthode
validate(payload)est exécutée et injecte les données décodées sous forme d’objet dans la requête HTTP :// Contenu injecté dans req.user req.user = { userId: payload.sub, email: payload.email, role: payload.role, countryId: user.countryId, // relu en base à chaque requête, pas signé dans le JWT };
[!IMPORTANT] Revalidation du compte à chaque requête :
JwtStrategy.validate()recharge l’utilisateur en base (usersService.findById) à chaque requête authentifiée, pas seulement au login. Siuser.isActiveestfalse(compte bloqué entre-temps par un admin), la requête échoue immédiatement avec401 Unauthorized ('ACCOUNT_BLOCKED'), même si le JWT est encore valide. C’est ce même mécanisme queGET /auth/meexpose aux frontends (apps/store/apps/dashboard) pour éjecter une session dont le compte vient d’être bloqué.
Fonctionnement Interne du RolesGuard
Le RolesGuard s’exécute après le guard d’authentification. Il récupère les rôles requis définis par le décorateur @Roles à l’aide du Reflector de NestJS :
const requiredRoles = this.reflector.getAllAndOverride<UserRole[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);Si des rôles sont requis, il extrait req.user et vérifie si le rôle de l’utilisateur fait partie de la liste autorisée :
return requiredRoles.some((role) => user?.role === role);5. Exemple Pratique : Protéger une Route pour les Vendeurs (SELLER)
Voici comment un développeur peut restreindre l’accès d’une route pour qu’elle soit réservée uniquement aux utilisateurs possédant le rôle SELLER (les vendeurs).
Étape A : Définition du Guard JWT (si non global)
Si vous ne l’avez pas encore défini dans vos fichiers de guard, voici la structure type du JwtAuthGuard basée sur Passport :
// apps/api/src/auth/guard/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}Étape B : Application du Décorateur et des Guards dans un Contrôleur
Pour sécuriser un endpoint spécifique, appliquez les décorateurs dans l’ordre suivant :
import { Controller, Post, Body, UseGuards, Req } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/guard/jwt-auth.guard';
import { RolesGuard } from '../auth/guard/roles.guard';
import { Roles } from '../auth/decorator/roles.decorator';
import { UserRole } from '@prisma/client';
import { CreateProductDto } from './dto/create-product.dto';
import { ProductsService } from './products.service';
@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
@Post()
@Roles(UserRole.SELLER) // 1. Spécifie le(s) rôle(s) autorisé(s)
@UseGuards(JwtAuthGuard, RolesGuard) // 2. Applique l'authentification ET la vérification de rôle
async createProduct(@Body() createProductDto: CreateProductDto, @Req() req: any) {
// req.user contient les informations décodées du JWT (userId, email, role)
const sellerId = req.user.userId;
return this.productsService.create(createProductDto, sellerId);
}
}[!WARNING] Ordre d’exécution des Guards : Il est impératif de déclarer
JwtAuthGuardavantRolesGuarddans le décorateur@UseGuards(JwtAuthGuard, RolesGuard). NestJS résout les guards de gauche à droite. Si vous placezRolesGuarden premier, il tentera d’évaluer le rôle sur un objetreq.usernon encore défini par le guard JWT, ce qui provoquera un accès refusé systématique (ou une erreur).
6. Résumé des Bonnes Pratiques de Sécurité pour l’Équipe
- Clé Secrète : Toujours injecter la clé
JWT_SECRETvia les variables d’environnement (.env) en production. Ne jamais versionner une clé en clair sur le dépôt git. - Expiration Courte : Préférer des durées d’expiration de token courtes combinées à un système de Refresh Token pour les sessions à long terme.
- Vérification double côté Prisma : Ne faites pas uniquement confiance au
RolesGuardpour la protection. Dans les requêtes Prisma d’écriture/modification, assurez-vous d’ajouter systématiquement un filtre de possession (ex:where: { id: productId, sellerId: currentUser.id }) pour éviter les vulnérabilités de type IDOR (Insecure Direct Object Reference). - Le rôle ne suffit pas toujours : un
SELLERqui passe leRolesGuardn’est pas nécessairement autorisé à tout faire —ProductsService.create()vérifie en plusUser.vendorStatusavant d’accepter la création d’un produit. Voir Validation des comptes vendeur : un vendeur auto-inscrit restePENDING(donc bloqué côté publication) tant qu’un admin ne l’a pas approuvé, même s’il possède un JWTSELLERvalide.