Sécurité

Sécuriser une API REST : JWT, HTTPS et OWASP en pratique

Un guide pratique pour blinder votre API REST : authentification JWT, HTTPS, validation des entrées, rate limiting, gestion des secrets et application concrète de l'OWASP API Top 10.

Pourquoi votre API est la première cible

Une API REST mal protégée, c’est une porte d’entrée grande ouverte sur votre base de données, vos utilisateurs et votre réputation. Que vous exposiez l’API d’une application de paiement mobile à Dakar ou d’une plateforme de e-commerce à Abidjan, les attaquants ne testent pas votre interface : ils visent directement vos endpoints. Dans cet article, nous allons sécuriser une API Node.js/Express de bout en bout, couche par couche, en appliquant concrètement les recommandations de l’OWASP API Security Top 10.

L’objectif est simple : à la fin, vous saurez authentifier vos clients avec des jetons JWT solides, chiffrer le transport, filtrer les entrées, limiter les abus et garder vos secrets hors de votre code. On y va étape par étape.

Étape 1 — Imposer HTTPS partout

Aucune authentification ne vaut quoi que ce soit sur du HTTP en clair : un jeton transmis sans chiffrement est interceptable sur n’importe quel réseau Wi-Fi partagé. Forcez le HTTPS et activez les en-têtes de sécurité avec helmet.

const express = require('express');
const helmet = require('helmet');
const app = express();

app.use(helmet()); // En-têtes sécurisés (HSTS, X-Frame-Options, etc.)

// Rediriger tout trafic HTTP vers HTTPS derrière un proxy
app.use((req, res, next) => {
  if (req.headers['x-forwarded-proto'] !== 'https') {
    return res.redirect(301, `https://${req.headers.host}${req.url}`);
  }
  next();
});

En production, terminez le TLS au niveau du reverse proxy (Nginx, Caddy) avec un certificat Let’s Encrypt gratuit, et activez HSTS pour que les navigateurs n’acceptent plus jamais le HTTP.

Étape 2 — Authentifier avec des JWT bien configurés

Le JSON Web Token est le standard de fait pour les API sans état. Le piège classique : signer le jeton avec un secret faible, oublier son expiration, ou stocker des données sensibles dans le payload (qui est seulement encodé en Base64, pas chiffré).

Générer un jeton à la connexion

const jwt = require('jsonwebtoken');

function genererToken(utilisateur) {
  return jwt.sign(
    { sub: utilisateur.id, role: utilisateur.role },
    process.env.JWT_SECRET,        // secret fort, jamais en dur
    { expiresIn: '15m', algorithm: 'HS256' }
  );
}

Vérifier le jeton sur chaque requête protégée

function auth(req, res, next) {
  const header = req.headers.authorization || '';
  const token = header.startsWith('Bearer ') ? header.slice(7) : null;
  if (!token) return res.status(401).json({ erreur: 'Token manquant' });

  try {
    req.utilisateur = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch (e) {
    return res.status(401).json({ erreur: 'Token invalide ou expiré' });
  }
}

Gardez les jetons d’accès à durée courte (15 minutes) et utilisez un refresh token à plus longue durée, stocké côté serveur et révocable. Ainsi, un jeton volé n’est exploitable qu’une poignée de minutes.

Conseil clé : ne confondez jamais authentification (qui es-tu ?) et autorisation (as-tu le droit ?). La faille n°1 de l’OWASP API Top 10, Broken Object Level Authorization, vient d’un endpoint qui authentifie l’utilisateur mais oublie de vérifier qu’il possède bien la ressource demandée. Vérifiez ressource.proprietaireId === req.utilisateur.sub à chaque accès.

Étape 3 — Valider et filtrer toutes les entrées

Toute donnée venant du client est hostile par défaut. La validation stricte coupe court aux injections (SQL, NoSQL) et aux données malformées. Utilisez un schéma déclaratif plutôt que des if éparpillés.

const { z } = require('zod');

const schemaInscription = z.object({
  email: z.string().email(),
  motDePasse: z.string().min(12),
  telephone: z.string().regex(/^+221d{9}$/) // format Sénégal
});

app.post('/inscription', (req, res) => {
  const resultat = schemaInscription.safeParse(req.body);
  if (!resultat.success) {
    return res.status(400).json({ erreurs: resultat.error.issues });
  }
  // ... données propres et typées dans resultat.data
});

Quelques réflexes complémentaires indispensables :

  • Utilisez toujours des requêtes paramétrées (prepared statements) ou un ORM : jamais de concaténation de chaînes SQL.
  • Appliquez une liste blanche des champs acceptés pour éviter le mass assignment (un client qui s’auto-attribue role: "admin").
  • Limitez la taille du corps des requêtes avec express.json({ limit: '10kb' }).
  • Renvoyez des messages d’erreur génériques : ne révélez ni la stack trace, ni la structure de la base.

Étape 4 — Limiter le débit (rate limiting)

Sans limitation, un seul script peut brute-forcer vos mots de passe ou saturer votre serveur. Le rate limiting protège contre l’abus, l’énumération de comptes et le déni de service.

const rateLimit = require('express-rate-limit');

const limiteurConnexion = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5,                    // 5 tentatives par IP
  message: { erreur: 'Trop de tentatives, réessayez plus tard' }
});

app.post('/connexion', limiteurConnexion, controleurConnexion);

Appliquez une limite stricte sur les endpoints sensibles (connexion, mot de passe oublié) et une limite plus large sur le reste de l’API.

Étape 5 — Protéger vos secrets

Un secret JWT, une clé d’API ou un mot de passe de base de données commités dans Git, c’est une fuite définitive : même supprimé, il reste dans l’historique. Externalisez tout dans des variables d’environnement et ne versionnez jamais le fichier .env.

# .gitignore
.env
*.pem

# Générer un secret JWT robuste (256 bits)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

En production, préférez un gestionnaire de secrets (Vault, AWS Secrets Manager, ou les variables d’environnement chiffrées de votre hébergeur). Faites tourner vos secrets régulièrement, surtout après le départ d’un membre de l’équipe.

Le piège fréquent à éviter

L’erreur la plus répandue est la fuite d’informations par sur-exposition de données (Excessive Data Exposure dans l’OWASP Top 10). Un développeur renvoie l’objet utilisateur complet — incluant le hash du mot de passe, le jeton de réinitialisation ou l’adresse interne — en se disant que « le front n’affiche que le nom ». Sauf que le client reçoit tout, et il suffit d’ouvrir l’onglet réseau pour le voir. Filtrez explicitement les champs renvoyés côté serveur :

res.json({ id: u.id, nom: u.nom, email: u.email }); // jamais u tout entier

Récapitulatif

Sécuriser une API REST n’est pas une fonctionnalité que l’on ajoute à la fin : c’est une discipline en couches. Retenez les cinq réflexes essentiels :

  • Chiffrer le transport avec HTTPS et HSTS, sans exception.
  • Authentifier via des JWT à courte durée, signés avec un secret fort.
  • Vérifier l’autorisation ressource par ressource, pas seulement l’identité.
  • Valider chaque entrée et limiter le débit des endpoints sensibles.
  • Externaliser les secrets et filtrer les données renvoyées.

Appliquez ces principes dès le premier endpoint et l’OWASP API Top 10 cessera d’être une liste abstraite pour devenir votre check-list quotidienne. Une API bien défendue, c’est la confiance de vos utilisateurs gagnée durablement.

Pour aller plus loin

Malick Diallo

Rédaction SenTur

Contributeur SenTur — passionné de tech et de transmission.

Aucun commentaire pour l'instant — lancez la discussion !

Laisser un commentaire

Votre adresse email ne sera pas publiée. La discussion est modérée — restez courtois et constructif.