Skip to Content
Architecture de Sécurité & Authentification

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 :

  1. 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.
  2. 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.role accepte 'BUYER' ou 'SELLER' — 'ADMIN' (ou toute autre valeur) retombe silencieusement sur BUYER. Un SELLER auto-inscrit est créé avec vendorStatus: PENDING et 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 sur POST /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 :

  1. JwtAuthGuard : Valide le jeton d’accès présent dans l’en-tête Authorization: Bearer <token>.
  2. RolesGuard : Vérifie si le rôle de l’utilisateur authentifié correspond aux rôles autorisés pour la route.
  3. 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 :

  1. Il extrait le token via la méthode ExtractJwt.fromAuthHeaderAsBearerToken().
  2. La stratégie jwt.strategy.ts valide la signature du token à l’aide de la clé secrète JWT_SECRET.
  3. 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. Si user.isActive est false (compte bloqué entre-temps par un admin), la requête échoue immédiatement avec 401 Unauthorized ('ACCOUNT_BLOCKED'), même si le JWT est encore valide. C’est ce même mécanisme que GET /auth/me expose 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 JwtAuthGuard avant RolesGuard dans le décorateur @UseGuards(JwtAuthGuard, RolesGuard). NestJS résout les guards de gauche à droite. Si vous placez RolesGuard en premier, il tentera d’évaluer le rôle sur un objet req.user non 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

  1. Clé Secrète : Toujours injecter la clé JWT_SECRET via les variables d’environnement (.env) en production. Ne jamais versionner une clé en clair sur le dépôt git.
  2. 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.
  3. Vérification double côté Prisma : Ne faites pas uniquement confiance au RolesGuard pour 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).
  4. Le rôle ne suffit pas toujours : un SELLER qui passe le RolesGuard n’est pas nécessairement autorisé à tout faire — ProductsService.create() vérifie en plus User.vendorStatus avant d’accepter la création d’un produit. Voir Validation des comptes vendeur : un vendeur auto-inscrit reste PENDING (donc bloqué côté publication) tant qu’un admin ne l’a pas approuvé, même s’il possède un JWT SELLER valide.
Last updated on