Intro
Lorsque vous développez des APIs, il est essentiel de suivre certaines règles pour garantir que les endpoints ne soient pas accessibles par des utilisateurs non autorisés. Plusieurs approches sont possibles :
- Vérifier manuellement les autorisations nécessaires au début de chaque endpoint.
- Créer des guards et utiliser des custom decorators pour rendre ces vérifications répétitives plus lisibles et moins verbeuses.
Quelques explications
Un Guard dans NestJS c’est une classe qui implémente l'interface CanActivate
avec une méthode appelée canActivate(context: ExecutionContext)
. Cette méthode reçoit le contexte d'exécution comme paramètre et retourne un booléen indiquant si l'accès au contrôleur ou endpoint est autorisé.

Un Custom Decorator dans NestJS est une expression qui retourne une fonction et peut recevoir des arguments comme une cible (target), un nom et un descripteur de propriété (property descriptor). Vous appliquez un decorator en le préfixant avec le caractère @
et en le plaçant au-dessus de ce que vous souhaitez décorer.
Les Decorators dans NestJS peuvent être appliqué sur une classe, une méthode ou même une propriété. NestJS met à disposition des Decorators comme @Param(myParam)
pour récupérer un paramètre d'une requête URL par exemple. Vous pourrez trouver la liste complète des Decorators disponibles juste ici.
Un Guard est un Custom Decorator qui peut être utilisé pour protéger un contrôleur ou un endpoint spécifique. Voici un exemple d'utilisateur essayant d'accéder à un module additionnel qu'il n'a pas payé.
Commençons d’abord par créer un auth guard pour injecter les informations de l'utilisateur dans la requête.
Nous supposons que l'objet utilisateur (user) contient une propriété nommée modules
, qui est une liste des modules auxquels l'utilisateur a accès.
Pour associer certains endpoints à un module spécifique, nous allons créer un decorator @UserModule
permettant de spécifier à quel module un contrôleur ou endpoint est lié.
Le code de ce décorateur est assez simple. On utilise la méthode createDecorator
de Reflector
pour ajouter une metadata au contrôleur auquel il sera assigné pour spécifier si les endpoints de ce contrôleur seront protégés par notre guard ou non.
user-modules.decorator.ts
import { SetMetadata } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
// Définition des types de modules
export type UserModule = 'management' | 'subscriptions' | 'roles';
// Création du decorator
export const UserModules = (modules: UserModule | UserModule[]) => SetMetadata('user-modules', modules);
// Ces 2 lignes sont identiques d'un point de vue fonctionnel, la seule chose qui change sera la façon de déclarer et de récupérer la valeur
export const UserModules = (modules: UserModule | UserModule[]) =>
Reflector.createDecorator<UserModule[]>('user-modules', modules);
Une fois que le décorateur est créé, nous devons créer le guard associé.
En ce qui concerne le code du guard, c’est relativement simple également. Nous allons récupérer les rôles qui sont injectés dans le décorateur et vérifier que l’utilisateur qui essaye d’accéder à la ressource possède les bons rôles.
user-modules.guard.ts
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { UserModules } from '[votre-architecture]/user-modules';
@Injectable()
export class UserModulesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// Si vous avez choisi SetMetadata
const modules = this.reflector.getAllAndOverride('user-modules', [context.getHandler(), context.getClass()]);
// Si vous avez choisi Reflector.createDecorator
const modules = this.reflector.getAllAndOverride<UserModule[]>('user-modules', [
context.getHandler(),
context.getClass(),
]);
// Si aucun module n'a été injecté, alors on laisse passer par défaut
if (!modules) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
const userModules = user.modules;
const canAccess = Array.isArray(modules)
? modules.every((module) => userModules.includes(module))
: userModules.includes(modules);
if (!canAccess) {
throw new UnauthorizedException("Vous ne possédez pas les droits nécessaires pour accéder à ce module.");
}
return true;
}
}
N'oubliez pas de déclarer le guard dans le module concerné. Vous pouvez l'appliquer à l'échelle d'un contrôleur, d'une méthode ou globalement.
Application au niveau du contrôleur
Ajoutez @UseGuards(UserModulesGuard)
au-dessus du contrôleur :
@UseGuards(UserModulesGuard)
export class YourController { ... }
Application globale
Ajoutez le guard dans module App :
app.module.ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { UserModulesGuard } from '[votre-architecture]/user-modules.guard';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: UserModulesGuard,
},
],
})
export class AppModule{}
Vous pouvez maintenant utiliser le decorator pour spécifier les modules nécessaires pour accéder à un endpoint :
@UserModules('subscriptions')
public getSubscriptionsList() { ... }
Et voilà ! Vous avez maintenant des endpoints sécurisés qui vérifient automatiquement si un utilisateur dispose des permissions nécessaires pour accéder aux données. N'hésitez pas à ajuster le code pour répondre à vos besoins spécifiques.