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

  1. Authentification : vérifier l'identité de l'utilisateur (JWT, OAuth)
  2. Autorisation : vérifier les permissions (RBAC, policies)
  3. Validation des données : rejeter les entrées malveillantes (Joi, Zod)
  4. Rate limiting : limiter les requêtes pour éviter les abus
  5. Protection HTTPS : chiffrer les communications
  6. Headers sécurisés : Helmet pour prévenir XSS, clickjacking...
  7. 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. 🔐🚀