Sécuriser son API Node.js : Guide complet 2025 (JWT, Helmet, Rate Limiting)
Guide complet pour sécuriser votre API Node.js en 2025 : JWT authentication, Helmet, rate limiting, validation, CORS. Protégez votre backend contre les attaques courantes avec ces bonnes pratiques.
Introduction : Pourquoi sécuriser son API est critique
En 2025, les APIs sont partout : applications mobiles, sites web, services cloud. Mais une API mal sécurisée est une porte ouverte aux hackers : vol de données, injection SQL, attaques DDoS, usurpation d'identité...
Selon l'OWASP, 80% des vulnérabilités web concernent les APIs. Pourtant, sécuriser une API Node.js/Express n'est pas si compliqué. Ce guide vous montre comment implémenter les bonnes pratiques essentielles : JWT, Helmet, rate limiting, validation, CORS, HTTPS...
À la fin de cet article, vous saurez construire une API Node.js résistante aux attaques courantes et conforme aux standards de sécurité 2025. 🔐
Architecture sécurisée : Les fondamentaux
Les 7 piliers de la sécurité API
- Authentification : vérifier l'identité de l'utilisateur (JWT, OAuth)
- Autorisation : vérifier les permissions (RBAC, policies)
- Validation des données : rejeter les entrées malveillantes (Joi, Zod)
- Rate limiting : limiter les requêtes pour éviter les abus
- Protection HTTPS : chiffrer les communications
- Headers sécurisés : Helmet pour prévenir XSS, clickjacking...
- Logging & monitoring : détecter les anomalies
Nous allons implémenter ces 7 piliers dans une API Express pas à pas.
1. Authentification avec JWT (JSON Web Tokens)
Pourquoi JWT ?
JWT est le standard de facto pour l'authentification d'API en 2025 :
- Stateless : pas besoin de stocker les sessions en base
- Scalable : fonctionne avec plusieurs serveurs (load balancing)
- Cross-platform : compatible mobile, web, desktop
- Compact : transporte des données utilisateur (claims)
Installation des dépendances
npm install jsonwebtoken bcrypt express dotenv
npm install --save-dev @types/jsonwebtoken @types/bcrypt
Génération d'un JWT
// auth.service.ts
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
const JWT_SECRET = process.env.JWT_SECRET!; // Stocké dans .env
const JWT_EXPIRES_IN = '1h'; // Expiration 1 heure
interface UserPayload {
userId: string;
email: string;
role: string;
}
export function generateToken(user: UserPayload): string {
return jwt.sign(
{
userId: user.userId,
email: user.email,
role: user.role
},
JWT_SECRET,
{ expiresIn: JWT_EXPIRES_IN }
);
}
export async function hashPassword(password: string): Promise {
return bcrypt.hash(password, 10); // Salt rounds = 10
}
export async function comparePassword(
password: string,
hashedPassword: string
): Promise {
return bcrypt.compare(password, hashedPassword);
}
Route de login
// auth.routes.ts
import express from 'express';
import { generateToken, comparePassword } from './auth.service';
import { User } from './user.model'; // Votre modèle utilisateur
const router = express.Router();
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
// 1. Vérifier que l'utilisateur existe
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ error: 'Identifiants invalides' });
}
// 2. Vérifier le mot de passe
const validPassword = await comparePassword(password, user.passwordHash);
if (!validPassword) {
return res.status(401).json({ error: 'Identifiants invalides' });
}
// 3. Générer le JWT
const token = generateToken({
userId: user.id,
email: user.email,
role: user.role
});
// 4. Retourner le token (dans cookie HTTP-only de préférence)
res.cookie('token', token, {
httpOnly: true, // Protège contre XSS
secure: process.env.NODE_ENV === 'production', // HTTPS seulement en prod
sameSite: 'strict', // Protège contre CSRF
maxAge: 3600000 // 1 heure en ms
});
res.json({ message: 'Connexion réussie', user: { email: user.email } });
} catch (error) {
res.status(500).json({ error: 'Erreur serveur' });
}
});
export default router;
Middleware de vérification JWT
// auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET!;
export interface AuthRequest extends Request {
user?: {
userId: string;
email: string;
role: string;
};
}
export function authMiddleware(
req: AuthRequest,
res: Response,
next: NextFunction
) {
try {
// 1. Récupérer le token (cookie ou header Authorization)
const token = req.cookies.token || req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Non authentifié' });
}
// 2. Vérifier et décoder le token
const decoded = jwt.verify(token, JWT_SECRET) as {
userId: string;
email: string;
role: string;
};
// 3. Attacher l'utilisateur à la requête
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Token invalide ou expiré' });
}
}
// Middleware pour vérifier le rôle
export function requireRole(role: string) {
return (req: AuthRequest, res: Response, next: NextFunction) => {
if (req.user?.role !== role) {
return res.status(403).json({ error: 'Accès interdit' });
}
next();
};
}
Utilisation du middleware
// app.ts
import express from 'express';
import { authMiddleware, requireRole } from './auth.middleware';
const app = express();
// Route publique
app.get('/public', (req, res) => {
res.json({ message: 'Route publique' });
});
// Route protégée (authentification requise)
app.get('/protected', authMiddleware, (req, res) => {
res.json({ message: 'Route protégée', user: req.user });
});
// Route admin (authentification + rôle admin)
app.get('/admin', authMiddleware, requireRole('admin'), (req, res) => {
res.json({ message: 'Zone admin' });
});
2. Helmet : Sécuriser les headers HTTP
Qu'est-ce que Helmet ?
Helmet est un middleware Express qui configure 12 headers HTTP sécurisés automatiquement pour protéger contre XSS, clickjacking, MIME sniffing, etc.
Installation
npm install helmet
Configuration Helmet
// app.ts
import express from 'express';
import helmet from 'helmet';
const app = express();
// Appliquer Helmet (configuration par défaut)
app.use(helmet());
// Configuration personnalisée
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000, // 1 an
includeSubDomains: true,
preload: true
}
}));
Headers configurés par Helmet
- Content-Security-Policy : empêche XSS en limitant les sources de contenu
- X-Frame-Options : protège contre clickjacking
- X-Content-Type-Options : empêche MIME sniffing
- Strict-Transport-Security : force HTTPS
- X-XSS-Protection : active le filtre XSS du navigateur
3. Rate Limiting : Prévenir les abus
Pourquoi le rate limiting ?
Le rate limiting limite le nombre de requêtes par IP/utilisateur pour éviter :
- Attaques DDoS
- Brute force (tentatives de login massives)
- Scraping abusif
- Spam
Installation
npm install express-rate-limit
Configuration
// app.ts
import rateLimit from 'express-rate-limit';
// Limiter général : 100 requêtes / 15 minutes par IP
const generalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requêtes max
message: 'Trop de requêtes, réessayez dans 15 minutes',
standardHeaders: true,
legacyHeaders: false,
});
// Limiter strict pour login : 5 tentatives / 15 minutes
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Trop de tentatives de connexion, réessayez dans 15 minutes',
skipSuccessfulRequests: true, // Ne compte que les échecs
});
app.use('/api/', generalLimiter);
app.use('/api/auth/login', loginLimiter);
4. Validation des données avec Zod
Pourquoi valider les entrées ?
Ne jamais faire confiance aux données utilisateur. La validation empêche :
- Injection SQL
- XSS (scripts malveillants)
- Overflow de mémoire
- Données corrompues en base
Installation de Zod
npm install zod
Exemple de validation
// validation.ts
import { z } from 'zod';
import { Request, Response, NextFunction } from 'express';
// Schéma de validation pour registration
const registerSchema = z.object({
email: z.string().email('Email invalide'),
password: z.string().min(8, 'Mot de passe trop court (min 8 caractères)')
.regex(/[A-Z]/, 'Doit contenir une majuscule')
.regex(/[0-9]/, 'Doit contenir un chiffre'),
name: z.string().min(2).max(50),
});
// Middleware de validation
export function validate(schema: z.ZodSchema) {
return (req: Request, res: Response, next: NextFunction) => {
try {
schema.parse(req.body);
next();
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
error: 'Validation échouée',
details: error.errors
});
}
next(error);
}
};
}
// Utilisation
app.post('/register', validate(registerSchema), async (req, res) => {
// req.body est maintenant validé et typé
const { email, password, name } = req.body;
// ...
});
5. CORS : Contrôler les origines autorisées
Configuration CORS sécurisée
import cors from 'cors';
const allowedOrigins = [
'https://monsite.com',
'https://www.monsite.com',
...(process.env.NODE_ENV === 'development' ? ['http://localhost:3000'] : [])
];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Non autorisé par CORS'));
}
},
credentials: true, // Autorise les cookies
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
6. Variables d'environnement sécurisées
Fichier .env
# .env
NODE_ENV=production
PORT=3000
JWT_SECRET=votre_secret_ultra_securise_32_caracteres_minimum
DATABASE_URL=postgresql://user:pass@localhost:5432/db
ALLOWED_ORIGINS=https://monsite.com,https://www.monsite.com
⚠️ Jamais de secrets en dur dans le code !
✅ Ajoutez .env dans .gitignore
7. HTTPS obligatoire en production
Redirection automatique vers HTTPS
// Force HTTPS en production
if (process.env.NODE_ENV === 'production') {
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`);
} else {
next();
}
});
}
8. Logging et monitoring
Logger les requêtes avec Morgan
import morgan from 'morgan';
// En développement : logs détaillés
if (process.env.NODE_ENV === 'development') {
app.use(morgan('dev'));
}
// En production : logs au format JSON
if (process.env.NODE_ENV === 'production') {
app.use(morgan('combined'));
}
Détecter les anomalies
- Utilisez Sentry pour tracker les erreurs
- Configurez des alertes sur taux d'erreur élevé
- Loggez les tentatives de login échouées
- Surveillez les pics de requêtes anormaux
Architecture complète sécurisée
// app.ts - API Node.js sécurisée complète
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
import morgan from 'morgan';
import cookieParser from 'cookie-parser';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
// 1. Helmet (headers sécurisés)
app.use(helmet());
// 2. CORS (origines autorisées)
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(','),
credentials: true
}));
// 3. Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
});
app.use('/api/', limiter);
// 4. Parsers
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// 5. Logging
app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
// 6. Routes
import authRoutes from './routes/auth.routes';
import userRoutes from './routes/user.routes';
app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes);
// 7. Gestion d'erreurs globale
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error(err.stack);
res.status(500).json({
error: 'Erreur serveur',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`API sécurisée démarrée sur port ${PORT}`);
});
Checklist sécurité API 2025
✅ Authentification & Autorisation
- JWT avec expiration courte (1h max)
- Refresh tokens pour renouvellement
- Mots de passe hashés avec bcrypt (salt rounds ≥ 10)
- RBAC (Role-Based Access Control)
✅ Protection des données
- HTTPS obligatoire en production
- Validation stricte avec Zod/Joi
- Sanitization des entrées (XSS, SQL injection)
- Secrets dans .env (jamais en dur)
✅ Protection contre les attaques
- Helmet pour headers sécurisés
- Rate limiting (100 req/15min général, 5 req/15min login)
- CORS restrictif (origines whitelistées)
- Protection CSRF avec tokens
✅ Monitoring & Logging
- Logs Morgan/Winston
- Tracking erreurs (Sentry, LogRocket)
- Alertes sur anomalies
- Audit des accès sensibles
Outils recommandés 2025
| Catégorie | Outils |
|---|---|
| Authentication | jsonwebtoken, passport.js, Auth0 |
| Validation | Zod, Joi, express-validator |
| Rate Limiting | express-rate-limit, rate-limiter-flexible |
| Headers Security | Helmet |
| Logging | Morgan, Winston, Pino |
| Monitoring | Sentry, LogRocket, DataDog |
| Testing Security | OWASP ZAP, Burp Suite, Postman |
Conclusion : La sécurité, un processus continu
Sécuriser une API Node.js en 2025 n'est pas une tâche ponctuelle, mais un processus continu. Les menaces évoluent, les bibliothèques ont des failles, les normes changent.
Ce que vous devez retenir :
- ✅ JWT pour l'authentification stateless
- ✅ Helmet pour sécuriser les headers HTTP
- ✅ Rate limiting pour prévenir les abus
- ✅ Validation stricte des entrées (Zod, Joi)
- ✅ HTTPS obligatoire en production
- ✅ Monitoring actif pour détecter les anomalies
En implémentant ces bonnes pratiques, vous réduisez de 80% les risques de vulnérabilités selon l'OWASP 2023. Votre API devient résistante aux attaques courantes : injection, XSS, DDoS, brute force...
Prochaines étapes : auditez régulièrement votre API avec des outils comme OWASP ZAP, maintenez vos dépendances à jour (npm audit), et formez votre équipe aux principes de sécurité.
La sécurité, c'est un état d'esprit, pas une feature. 🔐🚀