Introduction
Dans le paysage numérique, la transmission et la gestion sécurisées des données sont primordiales. En tant que colonne vertébrale des applications web modernes, les API (Application Programming Interfaces) facilitent la communication fluide entre divers composants logiciels, permettant l’échange d’informations cruciales. Cependant, cette commodité comporte des risques de sécurité inhérents, notamment en ce qui concerne l’accès non autorisé à des endpoints sensibles. C’est là que le JSON Web Token (JWT) semble être une bonne option. Nous allons l’utiliser dans une application NestJS.
NestJS
NestJS est un framework permettant de développer des applications Node.js efficaces et scalables. Il utilise JavaScript de manière progressive et prend en charge TypeScript nativement. — Doc officielle NestJS
NestJS propose une architecture prête à l’emploi qui permet aux développeurs et aux équipes de créer des applications hautement testables, évolutives, faiblement couplées et facilement maintenables. Cette architecture est basée sur une structure en trois couches (Contrôleur, Services, Accès aux données), mais il existe de nombreuses manières de construire une application NestJS.
L’un des aspects fondamentaux de NestJS est l’injection de dépendances, un modèle de conception qui permet de créer et de gérer des objets au sein d’un environnement applicatif. Dans NestJS, l’injection de dépendances est utilisée pour fournir des instances de services à d’autres composants de l’application.
Si vous souhaitez en savoir plus sur NestJS, je vous invite à consulter leur site web.
JWT (Json Web Token)
JWT encapsule les donnés utilisateur sous format JSON, garantissant leur intégrité et leur authenticité tout au long de leur cycle de vie. Ces jetons offrent une solution sans état, qui améliore la scalabilité et simplifie l’implémentation. Avec JWT, les développeurs peuvent authentifier les utilisateurs, gérer l’accès aux ressources et établir la confiance dans les interactions numériques.
Une fois le token JWT déchiffré, le JSON ressemble à ça:
{
"subject": "", // Email / ID du propriétaire du token, c’est l’identifiant principal
"iat": 42424242, // Date d’émission du token (Issued At)
"exp": 42428242, // Date d’expiration du token
// Ensuite, vous pouvez ajouter ce que vous souhaitez
"aud": "localhost", // Destination où le token est supposé être utilisé
"iss": "localhost", // L'entité qui a créé le token
"name": "John Doe",
"role": "user" // ...
}
Qui suis-je ?
Ravi de vous rencontrer ! Je suis Maxime, ingénieur logiciel chez Vaduo Consulting. Vaduo est une ESN conviviale basée à Lille. Son système collaboratif et bienveillant permet à chacun de s’exprimer et d’être écouté afin d’aider l’entreprise à évoluer. Vaduo accompagne ses clients locaux dans leurs problématiques techniques et agiles.
Sa mission est de remettre l’humain au cœur de la technologie, avec un véritable intérêt pour le bien-être, l’expertise et le développement de ses employés, ainsi qu’une relation saine avec ses partenaires.
Commençons !
Dans cet article, nous allons suivre une approche étape par étape pour construire un projet NestJS sécurisé en utilisant JWT. Bien entendu, pour réaliser ce projet, vous aurez besoin de Node.js et de votre IDE favori. Le dépôt GitHub associé à ce guide est disponible ici si vous souhaitez consulter directement le code : GitHub
Installation et configuration du projet
Installation de NestJS et des dépendances
- Installer la CLI NestJS et créer un projet :
npm install -g @nestjs/cli nest new nest-jwt
- Accéder au dossier du projet et installer les packages nécessaires :
cd nest-jwt npm install @nestjs/jwt @nestjs/config
- Générer les ressources d’authentification :
nest generate module auth nest generate controller auth nest generate service auth
Après avoir tout généré, on va pouvoir injecter notre AuthService dans le controller et créer l’endpoint de login.
auth.controller.ts
import { Controller, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
public constructor(private readonly authService: AuthService) {}
// POST /auth/login
@Post('login')
public login() {
return this.authService.login();
}
}
Mise en place de JWT
Nous allons utiliser @nestjs/jwt
pour créer notre token. Pour ce faire il faut créer un fichier de config.
- Création du fichier de config jwt :
import { registerAs } from '@nestjs/config';
const ONE_HOUR = 3600;
export default registerAs('jwt', () => {
return {
secret: 'VOUS_SECRET_KEY',
audience: 'localhost:3000',
issuer: 'localhost:3000',
accessTokenTtl: ONE_HOUR,
};
});
- Importer
ConfigModule.forRoot()
dans leAppModule
pour pouvoir importer n’importe quel fichier de configuration à l’intérieur de l’application.
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AuthModule } from './auth/auth.module';
import { AppService } from './app.service';
@Module({
imports: [
ConfigModule.forRoot(),
AuthModule
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
- Ajouter le
JwtModule
et leConfigModule
dans la partie “imports” de notreAuthModule
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import jwtConfig from './jwt.config';
@Module({
imports: [
JwtModule.registerAsync(jwtConfig.asProvider()),
ConfigModule.forFeature(jwtConfig),
],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
- Maintenant, que
JwtModule
est importé dans notreAuthModule
, on peut désormais injecterJwtService
ainsi que la config JWT dans notreAuthService
import { Inject, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigType } from '@nestjs/config';
import { randomUUID } from 'node:crypto';
import jwtConfig from './jwt.config';
@Injectable()
export class AuthService {
public constructor(
private readonly jwtService: JwtService,
@Inject(jwtConfig.KEY)
private readonly jwtConfiguration: ConfigType<typeof jwtConfig>,
) {}
public async login() {
// return a fake jwt token as long as this app is a template
return this.jwtService.signAsync(
{
sub: randomUUID(),
email: 'my-email@example.com',
},
{
audience: this.jwtConfiguration.audience,
issuer: this.jwtConfiguration.issuer,
secret: this.jwtConfiguration.secret,
expiresIn: this.jwtConfiguration.accessTokenTtl,
},
);
}
}
Pour vérifier que tout fonctionne comme il faut, vous pouvez lancer votre application avec npm start
. Vous pouvez maintenant tester votre endpoint API avec des outils tels que Postman, Bruno ou encore Insomnia.

Implémentation d’un Guard pour sécuriser des endpoints
Les gardes ont une responsabilité unique. Elles déterminent si une requête donnée est traitée par le gestionnaire de route ou non, en fonction de certaines conditions (comme les permissions, les rôles, les ACL, etc.) présentes au moment de l'exécution. — Doc officielle NestJS

Si vous souhaitez en apprendre plus sur les guards, un article est déjà disponible sur le blog juste ici.
Le but de notre Guard est de vérifier si notre token est toujours valide. Si c’est le cas, on injecte le contenu de notre token dans l’objet request dans la propriété user
.
Pour ce faire, il faut:
- Générer le fichier qui contiendra nos décorateurs
nest g decorator auth/decorators/auth
- Créer les décorateurs pour définir les routes publiques et protégées:
import { SetMetadata } from '@nestjs/common';
export const AUTH_GUARD_KEY = 'isProtected';
export const Public = () => SetMetadata(AUTH_GUARD_KEY, false);
export const Protected = () => SetMetadata(AUTH_GUARD_KEY, true);
- Générer les fichiers des Guards
nest generate guard auth/guards/access-token
nest generate guard auth/guards/public
Pour access-token.guard.ts, on cherche à déchiffrer notre token JWT et attacher son contenu à la propriété user
s’il est encore valide.
import {
CanActivate,
ExecutionContext,
Inject,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { ConfigType } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
import jwtConfig from '../jwt.config';
@Injectable()
export class AccessTokenGuard implements CanActivate {
public constructor(
private readonly jwtService: JwtService,
@Inject(jwtConfig.KEY)
private readonly jwtConfiguration: ConfigType<typeof jwtConfig>,
) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const request: Request = context.switchToHttp().getRequest();
const token = request.headers.authorization?.split(' ')?.[1] ?? undefined;
if (!token) {
throw new UnauthorizedException();
}
try {
// Attach the content of the jwt to the request
request.user = await this.jwtService.verifyAsync(
token,
this.jwtConfiguration,
);
} catch (e) {
throw new UnauthorizedException();
}
return true;
}
}
En ce qui concerne public.guard.ts, le code est assez explicite.
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class PublicGuard implements CanActivate {
// Vu que le guard public laisse tout passer, on retourne toujours "true"
public canActivate(context: ExecutionContext): boolean {
return true;
}
}
- Générons un Guard qui utilisera les 2 au dessus
nest generate guard auth/guards/auth --no-spec --flat
Le but de ce guard est simple, on regarde les metadata pour savoir si notre endpoint API est public ou non. Puis on appelle un guard ou l’autre selon cette information.
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AccessTokenGuard } from './access-token.guard';
import { PublicGuard } from './public.guard';
import { AUTH_GUARD_KEY } from '../decorators/auth.decorator';
const PROTECTED_ROUTE = true;
@Injectable()
export class AuthGuard implements CanActivate {
public constructor(
private readonly reflector: Reflector,
private readonly accessTokenGuard: AccessTokenGuard,
private readonly publicGuard: PublicGuard,
) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const isRouteProtected =
this.reflector.getAllAndOverride<boolean>(AUTH_GUARD_KEY, [
context.getHandler(),
context.getClass(),
]) ?? PROTECTED_ROUTE;
const guard = isRouteProtected ? this.accessTokenGuard : this.publicGuard;
let error = new UnauthorizedException();
const canActivate = await Promise.resolve(guard.canActivate(context)).catch(
(err) => {
error = err;
},
);
if (canActivate) {
return true;
}
throw error;
}
}
Application des guards
Pour pouvoir utiliser ces guards il faut les déclarer :
- Dans notre
AuthModule
:
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtModule } from '@nestjs/jwt';
import jwtConfig from './jwt.config';
import { APP_GUARD } from '@nestjs/core';
import { AccessTokenGuard } from './guards/access-token.guard';
import { ConfigModule } from '@nestjs/config';
import { AuthGuard } from './guards/auth.guard';
import { PublicGuard } from './guards/public.guard';
@Module({
imports: [
JwtModule.registerAsync(jwtConfig.asProvider()),
ConfigModule.forFeature(jwtConfig),
],
controllers: [AuthController],
providers: [
AuthService,
{
provide: APP_GUARD,
useClass: AuthGuard,
},
AccessTokenGuard,
PublicGuard,
],
})
export class AuthModule {}
- Dans le controller (on peut soit le déclarer à l’échelle de l’endpoint ou à l’échelle du controller)
import { Controller, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
import { Public } from './decorators/auth.decorator';
@Controller('auth')
export class AuthController {
public constructor(private readonly authService: AuthService) {}
// POST /auth/login
@Public()
@Post('login')
public login() {
return this.authService.login();
}
}
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { Protected } from './auth/decorators/auth.decorator';
@Protected()
@Controller()
export class AppController {
public constructor(private readonly appService: AppService) {}
// "/"
@Get()
public getHello(): string {
return this.appService.getHello();
}
}
Tester les endpoints
Si on essaye d’accéder à la route “/” on reçoit une 401 vu que cet endpoint est sécurisé et nécessite un token pour y accéder

Maintenant, on se connecte via la route “login”

Une fois le token récupéré, essayons à nouveau

Nous avons vu comment implémenter JWT pour sécuriser une application NestJS en utilisant des décorateurs, des Guards et des services d’authentification. Grâce à cette méthode, vous pouvez garantir que seuls les utilisateurs authentifiés peuvent accéder aux endpoints sécurisés. N’hésitez pas à modifier des parties du code pour correspondre à vos besoins.