Pourquoi Securiser une application NestJS ?
Dans un monde où les données personnelles et sensibles sont à la hauteur de la priorité, il est crucial de sécuriser toute application, y compris celles développées avec NestJS. En tant que développeur senior, vous avez probablement déjà confronté des situations où la sécurité a été compromise, par exemple lors d'un accès non autorisé ou une perte de données sensibles.
Un cas d'usage concret serait le développement d'une application de gestion de la santé en ligne. Si cette application n'était pas sécurisée, elle pourrait être vulnérable aux attaques telles que l'injection SQL ou les failles XSS (Cross-Site Scripting), ce qui pourrait entraîner une perte de confidentialité des informations des patients.
Prerequis
Connaissances nécessaires :
- Maîtrise avancée d'NestJS, en particulier le système d'injection de dépendance et les routes.
- Compréhension du principe de base de la sécurité Web (CSP, CORS, HTTPS).
- Connaissance des mécanismes de contrôle d'accès et de gestion des rôles.
Outils à installer :
- Node.js (v14.0.0 ou ultérieur)
- npm (Node Package Manager) pour gérer les dépendances
- NestJS CLI pour créer le projet
Concepts fondamentaux
1. Authentification et Autorisation
L'authentification est le processus par lequel un utilisateur est identifié, tandis que l'autorisation est le processus par lequel on vérifie si cet utilisateur a les droits nécessaires pour effectuer une action spécifique.
// auth.service.ts
import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
async validateUser(username: string, pass: string): Promise<any> {
const user = await this.findOne(username); // Méthode hypothétique pour récupérer un utilisateur
if (user && await bcrypt.compare(pass, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
async hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 10);
}
}
2. Middleware
Les middleware sont des fonctions qui ont accès à l'objet de requête (req), à l'objet de réponse (res) et à la fonction de rappel de la chaîne de middleware suivante dans le cycle de traitement d'une requête/réponse. Ils sont utilisés pour effectuer des tâches telles que la validation, la manipulation des données ou la gestion des erreurs.
// auth.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const token = req.headers['authorization'];
if (token && token === 'valid_token') {
next();
} else {
res.status(403).send({ message: 'Unauthorized' });
}
}
}
3. JWT (JSON Web Tokens)
Les tokens JWT sont des tokens de sécurité qui permettent d'assurer l'intégrité et la validité des données transmises entre le client et le serveur.
// jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'secret',
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
4. CORS (Cross-Origin Resource Sharing)
CORS est un mécanisme qui utilise des en-têtes HTTP pour indiquer que les ressources d'une application Web peuvent être accédées depuis une origine différente.
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as cors from 'cors';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(cors({
origin: 'http://example.com',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
}));
await app.listen(3000);
}
bootstrap();
Mise en pratique : projet fil rouge
Étape 1 : Création du projet NestJS
nest new task-manager
cd task-manager
npm install @nestjs/passport passport passport-jwt bcryptjs
Étape 2 : Configuration de l'authentification
Créez un service d'authentification.
// src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
async validateUser(username: string, pass: string): Promise<any> {
const user = await this.findOne(username); // Méthode hypothétique pour récupérer un utilisateur
if (user && await bcrypt.compare(pass, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
async hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 10);
}
private findOne(username: string): Promise<any> {
// Logique pour récupérer un utilisateur
return { username, password: 'hashed_password', id: 1 };
}
}
Étape 3 : Configuration du JWT
Créez un strategy de stratégie.
// src/auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'secret',
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
Étape 4 : Création du middleware d'authentification
// src/auth/auth.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const token = req.headers['authorization'];
if (token && token === 'valid_token') {
next();
} else {
res.status(403).send({ message: 'Unauthorized' });
}
}
}
Étape 5 : Ajout des routes sécurisées
// src/task/task.controller.ts
import { Controller, Get, UseGuards, Request } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Controller('task')
@UseGuards(AuthGuard('jwt'))
export class TaskController {
@Get()
getTasks(@Request() req) {
return `This action returns all tasks for ${req.user.username}`;
}
}
Erreurs frequentes et debugging
1. Token invalide
## ❌ Mauvais
res.status(403).send({ message: 'Unauthorized' });
## ✅ Correct
const token = req.headers['authorization'];
if (token && token === 'valid_token') {
next();
} else {
res.status(401).send({ message: 'Invalid token' });
}
2. Erreur de validation des données
## ❌ Mauvais
const user = await this.findOne(username); // Méthode hypothétique pour récupérer un utilisateur
if (user && await bcrypt.compare(pass, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
## ✅ Correct
try {
const user = await this.authService.validateUser(username, pass);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
return user;
} catch (error) {
throw error;
}
3. Erreur de configuration CORS
## ❌ Mauvais
app.use(cors());
## ✅ Correct
app.use(cors({
origin: 'http://example.com',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
}));
Pour aller plus loin
1. Authentification par Google ou Facebook
Pour intégrer l'authentification via des comptes sociaux populaires.
2. Gestion fine des rôles et permissions
Pour définir différents niveaux d'accès aux ressources de l'application.
3. Sécurité des données sensibles
Pour chiffrer les données sensibles stockées dans la base de données.
Défi pratique
Développez une API pour un blog avec les fonctionnalités suivantes :
- Authentification utilisateurs
- Création, lecture, mise à jour et suppression d'articles
- Gestion des commentaires
Conseils : Utilisez le système d'injection de dépendance d'NestJS pour structurer votre application. Assurez-vous que toutes les routes nécessitent une authentification valide.
Ce tutoriel vous aide à comprendre et mettre en œuvre les principes de base de la sécurité dans une application NestJS. En suivant ces étapes, vous serez prêt à sécuriser vos applications Web et protéger les données des utilisateurs.