REST API design - 23 - Sécurité : les attaques que tu vas subir (et comment t'en protéger)

Input sanitization, SQL injection, mass assignment, limites de taille, HTTPS, security headers et OWASP API Top 10.

REST API design 24 / 25
  1. 01 REST API design - 00 - Pourquoi le design de ton API change tout
  2. 02 REST API design - 01 - Les principes REST que personne ne lit
  3. 03 REST API design - 02 - Des URLs qui ont du sens
  4. 04 REST API design - 03 - Les méthodes HTTP, pour de vrai
  5. 05 REST API design - 04 - Les codes de statut HTTP qu'il faut connaître
  6. 06 REST API design - 05 - Body, headers et le diable dans les détails
  7. 07 REST API design - 06 - La pagination, ou comment ne pas tuer ta base
  8. 08 REST API design - 07 - Filtrage et tri sans prise de tête
  9. 09 REST API design - 08 - La validation avec Zod, gardien de ton API
  10. 10 REST API design - 09 - Erreurs : un format que tes clients vont adorer
  11. 11 REST API design - 10 - Authentification : JWT, API keys et OAuth2
  12. 12 REST API design - 11 - Versioning : quand et comment faire évoluer ton API
  13. 13 REST API design - 12 - CORS : comprendre et debugger les erreurs cross-origin
  14. 14 REST API design - 13 - Rate limiting : protéger ton API sans frustrer tes clients
  15. 15 REST API design - 14 - Caching : les bonnes réponses sont celles qu'on n'envoie pas
  16. 16 REST API design - 15 - Upload de fichiers : multipart, signed URLs et streaming
  17. 17 REST API design - 16 - Relations entre ressources : embarquer, lier ou les deux
  18. 18 REST API design - 17 - HATEOAS : des liens dans tes réponses
  19. 19 REST API design - 18 - OpenAPI : le contrat entre ton API et le monde
  20. 20 REST API design - 19 - Documentation : une API non documentee est une API inutile
  21. 21 REST API design - 20 - Testing : tester ton API sans devenir fou
  22. 22 REST API design - 21 - Webhooks : quand c'est ton API qui appelle
  23. 23 REST API design - 22 - Performance : quand chaque milliseconde compte
  24. 24 REST API design - 23 - Sécurité : les attaques que tu vas subir (et comment t'en protéger)
  25. 25 REST API design - 24 - Glossaire : tous les termes REST API en un seul endroit

23 - Sécurité : les attaques que tu vas subir (et comment t'en protéger)

Ce que tu vas apprendre

  • L'input sanitization et pourquoi la validation ne suffit pas
  • Les injections SQL et comment les éviter définitivement
  • Le mass assignment et le danger des ORM trop permissifs
  • Les limites de taille de requête
  • Les security headers HTTP
  • Le OWASP API Security Top 10

Prerequisites

Avoir lu l'article sur la performance. Avoir une API REST qui accepte des donnees utilisateur (donc toutes les APIs).


Ma première API publique a ete scannee par des bots moins de 24 heures apres sa mise en ligne. Des tentatives d'injection SQL dans les query params, des payloads JSON de 50 Mo pour faire crasher le serveur, des paths comme /../../../etc/passwd. Je n'avais prevu aucune de ces attaques. Quand tu mets une API sur internet, tu invites le monde entier a essayer de la casser. Autant s'y preparer.

Input sanitization

La regle numero un : ne fais jamais confiance aux donnees entrantes. Tout ce qui vient du client est suspect. Meme les headers. Meme le Content-Type.

La validation vérifié que les donnees ont le bon format. La sanitization nettoie les donnees dangereuses. Tu as besoin des deux.

typescriptimport { z } from "zod";
import xss from "xss";

const CreateProductSchema = z.object({
  name: z.string()
    .min(1)
    .max(200)
    .transform(val => xss(val)), // Supprime le HTML/JS
  description: z.string()
    .max(5000)
    .transform(val => xss(val)),
  price: z.number()
    .int()
    .positive()
    .max(1_000_000_00) // 1M euros max en centimes
});

La validation avec Zod rejette les donnees mal formees. Le transform avec xss nettoie le HTML potentiel. Un <script>alert('xss')</script> dans le nom d'un produit devient du texte inoffensif.

SQL injection

La SQL injection, c'est quand un attaquant inséré du SQL dans tes paramètres :

GET /users?name='; DROP TABLE users; --

Si tu construis ta requête avec de la concatenation de strings, c'est game over :

typescript// DANGER : injection SQL possible
const query = `SELECT * FROM users WHERE name = '${req.query.name}'`;

La solution définitive : les requêtes parametrees. Tous les ORM et tous les query builders les utilisent par défaut :

typescript// Avec un query builder (Drizzle)
const users = await db.select().from(usersTable)
  .where(eq(usersTable.name, req.query.name));

// Avec une requete parametree brute
const result = await pool.query(
  "SELECT * FROM users WHERE name = $1",
  [req.query.name]
);

Le $1 est un placeholder. La base de donnees traite la valeur comme une donnee, jamais comme du SQL. Fin de l'injection. Si tu utilises un ORM comme Drizzle ou Prisma, tu es protégé par défaut. Mais si tu ecris du SQL brut quelque part, utilise toujours des paramètres.

Mass assignment

Le mass assignment, c'est quand tu prends le body JSON du client et tu le passes directement a ta base de donnees :

typescript// DANGER : mass assignment
app.put("/users/:id", async (req, res) => {
  await db.update(usersTable).set(req.body).where(eq(usersTable.id, req.params.id));
});

Un attaquant envoie :

json{
  "name": "Alice",
  "role": "admin",
  "email_verified": true
}

Et se retrouve admin avec un email vérifié. La solution : un schema explicite qui n'accepte que les champs autorises.

typescriptconst UpdateUserSchema = z.object({
  name: z.string().min(1).max(100).optional(),
  email: z.string().email().optional(),
  avatar_url: z.string().url().optional()
  // Pas de "role", pas de "email_verified"
});

app.put("/users/:id", async (req, res) => {
  const data = UpdateUserSchema.parse(req.body);
  await db.update(usersTable).set(data).where(eq(usersTable.id, req.params.id));
});

Sur paltemps.fr, chaque endpoint a son schema Zod qui liste explicitement les champs acceptes. Pas d'exception.

Limites de taille

Un body JSON de 100 Mo va consommer toute la mémoire de ton serveur. Configure des limites partout :

typescript// Express
app.use(express.json({ limit: "1mb" }));

// Nginx
// client_max_body_size 5m;

Pour les query strings aussi. Un ?ids=1,2,3,...,100000 peut faire exploser un IN SQL. Limite le nombre d'éléments :

typescriptconst ids = req.query.ids?.split(",");
if (ids && ids.length > 100) {
  return res.status(400).json({ detail: "Maximum 100 IDs." });
}

Et les headers. Un header Cookie de 1 Mo, ca existe dans les attaques. Nginx limite ca par défaut (large_client_header_buffers), mais vérifié ta configuration.

HTTPS obligatoire

En 2026, une API sans HTTPS, ca ne devrait plus exister. HTTPS chiffre tout le trafic entre le client et le serveur. Sans HTTPS :

  • Les tokens d'authentification transitent en clair
  • Un attaquant sur le meme réseau wifi peut lire toutes les requêtes
  • Les intermediaires (FAI, proxys) peuvent modifier les réponses

Avec Let's Encrypt, un certificat SSL est gratuit et s'installe en une commande. Pas d'excuse.

Ajoute aussi le header HSTS pour forcer HTTPS :

Strict-Transport-Security: max-age=31536000; includeSubDomains

Security headers

Des headers a ajouter a toutes tes réponses :

typescriptapp.use((req, res, next) => {
  res.set("X-Content-Type-Options", "nosniff");
  res.set("X-Frame-Options", "DENY");
  res.set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'");
  res.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
  res.set("X-XSS-Protection", "0"); // Desactive le filtre XSS des vieux navigateurs (cause plus de problemes qu'il n'en resout)
  next();
});

En Express, le package helmet fait tout ca en une ligne :

typescriptimport helmet from "helmet";
app.use(helmet());

OWASP API Security Top 10

L'OWASP publie un top 10 des vulnérabilités spécifiques aux APIs. Les plus courantes :

  1. Broken Object Level Authorization : un user accede aux donnees d'un autre user en changeant l'ID dans l'URL. Verifie toujours que le user a le droit d'acceder a la ressource demandee.

  2. Broken Authentication : tokens qui n'expirent pas, pas de rate limit sur le login, tokens stockes en clair.

  3. Broken Object Property Level Authorization : le mass assignment dont on a parle plus haut.

  4. Unrestricted Resource Consumption : pas de rate limiting, pas de limites de taille, pas de pagination.

  5. Broken Function Level Authorization : un user normal qui accede a un endpoint admin parce que tu as oublie le middleware d'autorisation.

typescript// Verifie TOUJOURS l'autorisation
app.delete("/users/:id", requireAuth, requireRole("admin"), async (req, res) => {
  // ...
});

La checklist sécurité minimum

  • HTTPS partout, avec HSTS
  • Requetes parametrees (jamais de concatenation SQL)
  • Schemas de validation sur chaque endpoint (Zod, Typebox)
  • Rate limiting sur tous les endpoints, renforce sur login/register
  • Limites de taille sur body, query string, headers
  • Security headers (helmet ou équivalent)
  • Verification d'autorisation sur chaque ressource (pas juste l'authentification)
  • Tokens avec expiration et rotation
  • Logs des tentatives d'acces echouees

Résumé

  • Sanitize et valide toutes les entrees avec des schemas explicites (Zod)
  • Les requêtes parametrees eliminent les injections SQL définitivement
  • Le mass assignment se resout avec des schemas qui listent les champs autorises
  • Limite la taille des requêtes a tous les niveaux (body, query, headers)
  • HTTPS et security headers sont le minimum absolu
  • Consulte le OWASP API Top 10 et vérifié chaque point

Article précédent : Performance Article suivant : Glossaire

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.