Files
_Assistant_Lead_Tech/knowledge/backend/risques/auth.md
MaksTinyWorkshop 7767f1f947 capitalisation: intégration 28 entrées knowledge + 2 CLAUDE.md RL799_V2 (triage branche mcp_v1)
28 nouvelles sections intégrées dans 12 fichiers knowledge (backend risques/patterns,
frontend risques/patterns, workflow risques). Couvre rate limiting, RGPD, CSP Next.js,
refresh token TOCTOU, catch-all Prisma, distinction 401/403, tests E2E Playwright, etc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 20:11:02 +02:00

13 KiB

title: Backend — Risques & vigilance : Auth domain: backend bucket: risques tags: [auth, guards, request-user, sessions, admin] applies_to: [implementation, review, debug] severity: high validated_on: 2026-04-07 source_projects: [app-alexandrie, RL799_V2]

Backend — Risques & vigilance : Auth

Extrait de la base de connaissance Lead_tech. Voir knowledge/backend/risques/README.md pour l'index complet.


AuthN/AuthZ dispersée (contrôles d'accès au fil de l'eau)

Risques

  • Règles de permissions incohérentes selon endpoints
  • Failles "oubliées" sur un endpoint secondaire
  • Audit impossible

Symptômes

  • Utilisateurs qui accèdent à des ressources non prévues
  • Correctifs en urgence "on ajoute un if ici"
  • Bugs qui réapparaissent après refactor

Bonnes pratiques / mitigations

  • Centraliser authn/authz (middleware/policies)
  • Tests sur règles critiques
  • Logs/audit des décisions d'accès

Guard global manquant (request.user jamais peuplé)

Risques

  • Chaîne auth bâtie sur une fondation inopérante (tout "a l'air OK" en dev/tests, mais casse en prod)
  • Guards aval qui dépendent de request.user en erreur (ou contournements involontaires)
  • Découvert tard (souvent uniquement en code review ou en prod)

Symptômes

  • request.user vaut undefined dans un guard supposé "après auth"
  • Endpoints qui passent alors qu'ils devraient être refusés (si les guards aval se désactivent/retournent true par défaut)
  • Tests "verts" car trop mockés (pas de test e2e qui valide le pipeline complet)

Bonnes pratiques / mitigations

  • Poser explicitement le guard global dès les foundations (au moins AuthGuard)
  • Vérifier l'ordre des APP_GUARD (AuthGuard avant tout guard qui lit request.user)
  • Ajouter au minimum 1 test d'intégration/e2e qui prouve que request.user est bien peuplé sur un endpoint protégé

Guard NestJS route-level — null-check manquant sur request.user

Risques

  • Un guard route-level qui lit request.user.userId sans null-check lève une TypeError (500) si request.user est absent
  • Mauvaise registration de module, test d'intégration mal configuré, ou middleware custom peuvent produire cet état

Symptômes

  • TypeError: Cannot read properties of undefined (reading 'userId') en prod
  • Tests "verts" car request.user mocké globalement, mais pas le guard isolé

Bonnes pratiques / mitigations

const user = (request as any).user as { userId: string } | undefined;
if (!user?.userId) {
  throw new UnauthorizedException({ error: { code: 'UNAUTHENTICATED', message: '...' } });
}
  • Règle : les guards route-level ne font pas confiance aux guards globaux pour leurs invariants — ils se défendent eux-mêmes.
  • Contexte technique : NestJS v10+ — 09-03-2026

Risques

  • Si la révocation DB échoue avant la suppression du cookie, l'utilisateur garde un cookie local devenu incohérent
  • L'utilisateur peut rester bloqué dans un état où il ne peut plus se déconnecter proprement
  • Le comportement diffère selon la disponibilité de la base

Symptômes

  • Logout qui échoue par intermittence quand la DB est instable
  • Cookie de session toujours présent côté navigateur après erreur serveur
  • Réessais de logout qui produisent des états difficiles à diagnostiquer

Bonnes pratiques / mitigations

  • Toujours supprimer le cookie en premier, même si la révocation DB échoue ensuite
  • Traiter la suppression côté DB en best-effort ou avec gestion d'idempotence adaptée
  • Vérifier en test qu'un échec DB ne laisse pas l'accès browser actif
  • Contexte technique : Next.js / auth par cookie / session persistée — 16-03-2026

Endpoints GET sans contrôle d'accès sur ressource protégée

Risques

  • Un endpoint de lecture expose des données premium/protégées à tout utilisateur authentifié
  • La règle "seuls les writes vérifient les droits" est un anti-pattern qui cause des fuites silencieuses

Symptômes

  • getCategories, getThreads ou équivalent accessible sans vérification d'entitlements
  • Endpoint write protégé par assertForumAccess mais GET correspondant non protégé

Bonnes pratiques / mitigations

  • Tout endpoint retournant des données liées à une ressource protégée (forum pack, contenu premium) doit appeler assertForumAccess ou équivalent, même pour les GET

  • Checklist review : pour chaque nouveau GET, vérifier qu'il passe par le guard/helper d'accès si la ressource appartient à un scope protégé

  • Contexte technique : NestJS / app-alexandrie — 23-03-2026


NestJS @UseGuards(AdminRoleGuard) sans @RequireAdminRole() — silencieusement ouvert

Risques

  • AdminRoleGuard.canActivate() lit la metadata REQUIRE_ADMIN_ROLE_KEY posée par @RequireAdminRole()
  • Si le décorateur est absent, requiresAdmin = false/undefined → le guard retourne true et laisse passer sans vérification

Symptômes

  • Endpoint admin accessible à tout utilisateur authentifié
  • Zéro erreur de compilation ou de démarrage — le bug est silencieux

Bonnes pratiques / mitigations

// ✅ Correct — les deux décorateurs ensemble
@Post('admin/ressource')
@UseGuards(AdminRoleGuard)
@RequireAdminRole()
async createRessource(...) {}

// ❌ Silencieusement non protégé — @RequireAdminRole() manquant
@Post('admin/ressource')
@UseGuards(AdminRoleGuard)
async createRessource(...) {}
  • Règle : s'applique à tout guard NestJS qui délègue la décision à une metadata de décorateur

  • Checklist review : vérifier systématiquement les endpoints admin que @RequireAdminRole() est présent

  • Contexte technique : NestJS / guards metadata — app-alexandrie 23-03-2026


Mock Prisma session sans filtre expiresAt — divergence test/prod

Risques

  • Le mock session.findFirst omet de filtrer expiresAt → des sessions expirées passent en test alors qu'elles seraient rejetées en prod
  • Masque des régressions sur la logique d'expiration de session

Symptômes

  • Tests e2e verts avec un token de session expiré
  • Bug découvert uniquement en prod quand la TTL est dépassée

Bonnes pratiques / mitigations

Le mock doit répliquer tous les critères de getUserByToken() en prod : revokedAt === null ET expiresAt > now :

// ✅ Mock complet fidèle à la prod
findFirst: jest.fn().mockImplementation(({ where }) => {
  const session = store[where.accessToken];
  if (!session) return null;
  if (where.revokedAt === null && session.revokedAt !== null) return null;
  if (where.expiresAt?.gt && session.expiresAt <= where.expiresAt.gt) return null;
  return session;
})
  • Règle : seedSession() doit initialiser expiresAt à +30j par défaut. Ajouter un helper seedExpiredSession() si des tests de session expirée sont nécessaires.

  • Contexte technique : NestJS / Prisma mock / e2e — app-alexandrie 24-03-2026


Tests e2e autorisation : scénarios non-abonné avec buildApp partagé

Risques

  • Un describe e2e avec buildApp partagé en beforeAll (entitlements actifs) rend impossible le test de scénarios non-abonné sans pollution entre tests
  • Tenter de surcharger le mock partagé (jest.fn().mockResolvedValueOnce(...)) dans un it intermédiaire est fragile et crée des effets de bord

Symptômes

  • Scénario "non-abonné → 403" n'est jamais testé, ou pollue les autres tests si le mock est modifié en cours de describe

Bonnes pratiques / mitigations

Créer une instance buildApp isolée pour les scénarios d'autorisation alternatifs :

it('retourne 403 si subscription inactive', async () => {
  const isolatedApp = await buildApp({
    getEntitlementsForUser: jest.fn().mockResolvedValue({
      subscription: { isActive: false, plan: 'free' }
    })
  });
  // ... tests
  await isolatedApp.close();
});
  • Règle : ne jamais tenter de surcharger un mock partagé dans un it — créer un buildApp isolé avec app.close() en fin de test

  • Contexte technique : NestJS / Jest e2e — app-alexandrie 24-03-2026


Champ métier absent du JWT — découplage silencieux frontend/backend

Risques

  • Le frontend lit un champ dans decodeJwtPayload(token) qui n'est jamais émis par le service d'authentification
  • Le comportement est silencieux — undefined est traité comme '', aucune erreur visible, l'UI se dégrade sans signal d'alerte

Symptômes

  • decodeJwtPayload(token).field retourne undefined pour tous les utilisateurs réels
  • Filtres de grade/rôle côté UI entièrement inopérants (rank=0, aucune tab affichée)

Bonnes pratiques / mitigations

  • Toute donnée lue via decodeJwtPayload côté frontend doit être explicitement émise dans le payload JWT côté backend

  • Lors de l'ajout d'un champ à JwtPayload (type TypeScript), vérifier immédiatement que le service d'authentification inclut ce champ à l'émission

  • Ajouter un test d'intégration login → decode qui vérifie la présence des champs critiques dans le token retourné

  • Signal review : un champ apparaît dans le type JwtPayload côté frontend sans modification correspondante dans authService.ts

  • Contexte technique : JWT / auth — RL799_V2 02-04-2026


Confusion email login / email de contact dans un endpoint annuaire

Risques

  • Le mapping de l'endpoint annuaire utilise email: user.email (email de login, toujours présent) alors que l'intention est d'exposer un email de contact optionnel
  • Même un utilisateur à bas privilège peut récupérer les emails de login de tous les membres

Symptômes

  • email: user.email dans le mapping d'un endpoint de type "annuaire" ou "liste membres"
  • Emails de connexion exposés à tous les utilisateurs authentifiés

Bonnes pratiques / mitigations

  • Dans tout endpoint annuaire ou profil public, distinguer explicitement :

    • email de login : identifiant de compte, JAMAIS exposé à un tiers dans un endpoint annuaire
    • email de contact : champ optionnel dans le profil ou la table directory, exposé uniquement s'il est renseigné
  • Si le modèle ne dispose pas encore d'un champ email de contact distinct, renvoyer undefined pour le champ email dans l'annuaire

  • Signal review : email: user.email dans le mapping d'un endpoint annuaire

  • Contexte technique : auth / annuaire — RL799_V2 02-04-2026


TOCTOU sur rotation de refresh token

Risques

  • Un pattern findUnique + update séparés sur la rotation de refresh token crée une fenêtre TOCTOU
  • Deux requêtes concurrentes avec le même refresh token passent toutes les deux la vérification avant que l'une ne révoque → deux sessions valides émises, le vol de token passe inaperçu

Symptômes

  • Deux sessions actives issues du même refresh token
  • Détection de vol impossible car les deux tokens sont valides

Bonnes pratiques / mitigations

  • Toujours utiliser un updateMany atomique avec condition WHERE revokedAt IS NULL AND expiresAt > NOW() et vérifier count === 1

  • Si count === 0, le token a déjà été utilisé → révoquer tous les tokens du user (token family detection, RFC 6819)

  • Signal review : findUnique suivi de update séparés dans un flux de rotation de refresh token

  • Contexte technique : auth / refresh token — RL799_V2 08-04-2026


Drift d'authentification par copier-coller de pattern auth

Risques

  • Quand un helper d'auth centralisé existe (requireRoleAccess), mais que de nouveaux services réimplémentent le même pattern manuellement (extractAccessToken + verifyToken + vérification locale), chaque service développe ses propres variantes (codes d'erreur différents, 401 vs 403, requestId ou non)
  • La surface d'auth devient incohérente et indéfendable en audit

Symptômes

  • Un audit RBAC révèle qu'une part significative des routes ont un pattern d'auth "fait maison" au lieu du helper standard
  • Codes d'erreur divergents entre services pour la même situation (token absent)

Bonnes pratiques / mitigations

  • Tout nouveau handler HTTP DOIT utiliser le helper centralisé pour l'authentification et l'autorisation

  • Ne JAMAIS importer extractAccessToken + verifyToken directement dans un service métier

  • Si le helper ne couvre pas un besoin (ex: besoin de userId en plus de email), étendre le helper plutôt que contourner

  • Signal review : import de verifyToken dans un fichier service (hors authHelpers.ts)

  • Contexte technique : auth / architecture — RL799_V2 08-04-2026