8.2 KiB
Backend — Risques & vigilance : Auth
Extrait de la base de connaissance Lead_tech. Voir
knowledge/backend/risques/README.mdpour 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.useren erreur (ou contournements involontaires) - Découvert tard (souvent uniquement en code review ou en prod)
Symptômes
request.uservautundefineddans 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 litrequest.user) - Ajouter au minimum 1 test d'intégration/e2e qui prouve que
request.userest 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.userIdsans null-check lève uneTypeError(500) sirequest.userest 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.usermocké 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
Suppression du cookie après révocation DB sur logout
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,getThreadsou équivalent accessible sans vérification d'entitlements- Endpoint write protégé par
assertForumAccessmais 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
assertForumAccessou é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 metadataREQUIRE_ADMIN_ROLE_KEYposée par@RequireAdminRole()- Si le décorateur est absent,
requiresAdmin = false/undefined→ le guard retournetrueet 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.findFirstomet de filtrerexpiresAt→ 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 initialiserexpiresAtà +30j par défaut. Ajouter un helperseedExpiredSession()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
describee2e avecbuildApppartagé enbeforeAll(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 unitintermé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 unbuildAppisolé avecapp.close()en fin de test -
Contexte technique : NestJS / Jest e2e — app-alexandrie 24-03-2026