Protéger votre application Node.js contre les attaques courantes avec CORS et le middleware Helmet. Configuration pratique et bonnes practices.
Introduction à la sécurité Node.js
La sécurité est un pilier fondamental pour toute application Node.js en production. Les attaques web évoluent constamment : injection SQL, XSS, CSRF, clickjacking, etc.
Points clés :
- CORS : contrôle d'accès entre origines (domaines différents)
- Helmet : fixe les headers HTTP de sécurité
- HTTP Headers : first line of defense
- Validation : toujours valider les entrées utilisateur
CORS (Cross-Origin Resource Sharing)
CORS est un mécanisme qui permet à un navigateur d'accéder à des ressources d'une autre origine (domaine, port ou schéma différent).
Scénario sans CORS :
// Votre SPA Angular sur https://app.example.com
// Appelle votre API sur https://api.example.com
// ❌ BLOCKED par le navigateur (Same-Origin Policy)
Scénario avec CORS :
// Votre API répond avec les headers CORS appropriés
// Access-Control-Allow-Origin: https://app.example.com
// ✅ Navigateur accepte la requête
Configuration CORS pratique
Installation :
npm install cors
Base simple (autorise tous les domaines) :
const express = require('express');
const cors = require('cors');
const app = express();
// ⚠️ Attention : autorise TOUS les domaines
app.use(cors());
app.get('/api/data', (req, res) => {
res.json({ message: 'Données publiques' });
});
app.listen(3000);
Configuration avec whitelist (recommandé) :
const corsOptions = {
origin: ['https://app.example.com', 'https://admin.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true,
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
// Ou appliquer CORS sur des routes spécifiques
app.get('/api/public', cors(), (req, res) => {
res.json({ data: 'Public' });
});
app.post('/api/protected', cors(corsOptions), (req, res) => {
res.json({ data: 'Protected' });
});
Helmet : protection contre les attaques
Helmet configure les headers HTTP de sécurité pour protéger contre :
- XSS (Cross-Site Scripting) - injection de scripts malveillants
- Clickjacking - dissimuler votre app dans une iframe
- MIME sniffing - exécuter du contenu comme un autre type
- Insuffisance de HTTPS - forcer HTTPS via HSTS
Installation :
npm install helmet
Usage simple :
const express = require('express');
const helmet = require('helmet');
const app = express();
// Active tous les middleware Helmet par défaut
app.use(helmet());
app.get('/api/data', (req, res) => {
res.json({ secure: true });
});
app.listen(3000);
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Security-Policy: base-uri 'self'; font-src 'self' https:; ...
Referrer-Policy: no-referrer
HTTP Headers essentiels
Voici les headers de sécurité les plus importants que Helmet configure :
1. Strict-Transport-Security (HSTS)
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
// Force HTTPS pendant 1 an, inclut sous-domaines et preload list
2. X-Content-Type-Options
X-Content-Type-Options: nosniff
// Empêche le navigateur de "deviner" le type MIME
3. X-Frame-Options (Clickjacking)
X-Frame-Options: DENY
// Empêche l'app d'être chargée dans une iframe
4. Content-Security-Policy (CSP)
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; img-src *
// Définit les sources autorisées pour chaque type de ressource
5. Referrer-Policy
Referrer-Policy: strict-origin-when-cross-origin
// Contrôle l'envoi du referrer
Combiner CORS et Helmet
La meilleure pratique est d'utiliser CORS et Helmet ensemble :
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const app = express();
// 1. Sécurité des headers HTTP
app.use(helmet());
// 2. Contrôle d'accès multi-domaines
const corsOptions = {
origin: process.env.NODE_ENV === 'production'
? ['https://app.example.com', 'https://admin.example.com']
: 'http://localhost:4200',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400
};
app.use(cors(corsOptions));
// 3. Parsing JSON sécurisé
app.use(express.json({ limit: '10kb' })); // Limiter la taille
// 4. Routes
app.get('/api/data', (req, res) => {
res.json({ secure: true, cors: 'enabled' });
});
app.listen(3000, () => {
console.info(('Serveur sécurisé lancé sur port 3000');
});
Configuration production vs développement
Développement (permissif) :
// .env.development
CORS_ORIGIN=http://localhost:4200
NODE_ENV=development
// app.js
const corsOptions = {
origin: process.env.CORS_ORIGIN,
credentials: true
};
if (process.env.NODE_ENV === 'development') {
app.use(cors(corsOptions));
}
Production (restrictif) :
// .env.production
CORS_ORIGIN=https://app.example.com,https://admin.example.com
NODE_ENV=production
// app.js
const helmet = require('helmet');
const cors = require('cors');
app.use(helmet({
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ['*']
}
}
}));
const allowedOrigins = process.env.CORS_ORIGIN.split(',');
app.use(cors({
origin: allowedOrigins,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
- ✅ HTTPS obligatoire (NODE_ENV=production le force)
- ✅ HSTS préload enabled
- ✅ CSP stricte
- ✅ CORS whitelist pour domaines connus
- ✅ X-Frame-Options: DENY
Bonnes pratiques de sécurité
1. Validez TOUJOURS les entrées utilisateur
const { body, validationResult } = require('express-validator');
app.post('/api/user', [
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 })
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Traiter...
});
2. Utilisez des variables d'environnement, jamais hardcodez les secrets
// ❌ Mauvais
const secret = 'super-secret-key-12345';
// ✅ Bon
const secret = process.env.JWT_SECRET;
require('dotenv').config();
3. Limitez la taille des requêtes
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ limit: '10kb' }));
4. Rattrapage d'erreurs global
app.use((err, req, res, next) => {
console.error(err.stack);
// Ne pas exposer les détails d'erreur en production
const message = process.env.NODE_ENV === 'production'
? 'Une erreur interne s\'est produite'
: err.message;
res.status(err.status || 500).json({ error: message });
});
5. Rate limiting pour prévenir les attaques par brute force
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 min
max: 100 // limit 100 requests par window
});
app.use('/api/', limiter);