Sécuriser ses API REST/GraphQL — Bonnes pratiques essentielles
Pendant six mois, j’ai testé plusieurs environnements vulnérables (CRAPI, VAPI, DVGA) et accompagné des équipes de développement sur la sécurisation de leurs API. Voici une synthèse des vulnérabilités les plus fréquentes — et surtout comment les corriger.
Le paysage des risques API
L’OWASP API Security Top 10 reste la référence. Les trois failles que je rencontre le plus souvent :
| # | Vulnérabilité | Fréquence |
|---|---|---|
| 1 | Broken Object Level Authorization (BOLA) | 🔴 Très élevée |
| 2 | Injection (SQL, NoSQL, command) | 🔴 Élevée |
| 3 | Broken Authentication | 🟡 Moyenne |
1. BOLA — Broken Object Level Authorization
Le problème
Un utilisateur accède à /api/orders/123 — mais rien ne vérifie qu’il est bien le propriétaire de la commande 123. Il lui suffit de changer le 123 en 124, 125… pour lire les données des autres.
C’est l’équivalent API de l’IDOR web. C’est la vulnérabilité la plus courante sur les API.
Démo (style CRAPI)
GET /api/orders/1001 HTTP/1.1
Authorization: Bearer <token_user_A>
→ Réponse : données de la commande 1001 (appartenant à user_A). ✅
GET /api/orders/1002 HTTP/1.1
Authorization: Bearer <token_user_A>
→ Réponse : données de la commande 1002 (appartenant à user_B). ❌ BOLA !
Le correctif
Toujours vérifier l’ownership côté serveur — jamais faire confiance au client.
// ❌ MAL
app.get('/api/orders/:id', auth, async (req, res) => {
const order = await Order.findById(req.params.id);
res.json(order);
});
// ✅ BIEN
app.get('/api/orders/:id', auth, async (req, res) => {
const order = await Order.findOne({
_id: req.params.id,
userId: req.user.id, // ownership check
});
if (!order) return res.status(404).json({ error: 'Not found' });
res.json(order);
});
2. Injections
SQL Injection
Toujours utiliser des requêtes paramétrées :
// ❌ MAL — concatenation
const q = `SELECT * FROM users WHERE name = '${name}'`;
// ✅ BIEN — paramétré
const q = 'SELECT * FROM users WHERE name = $1';
db.query(q, [name]);
NoSQL Injection (MongoDB)
Inscription : {"username": {"$ne": null}} peut matcher tous les users !
// ❌ MAL
User.find({ username: req.body.username });
// ✅ BIEN — valider le type
if (typeof req.body.username !== 'string') {
return res.status(400).json({ error: 'Invalid' });
}
GraphQL — Depth & Complexity
Sur GraphQL (DVGA en demo), les attaques par profondeur et complexité peuvent DoSer le serveur :
# Bomb récursive
query { user { friends { user { friends { user { ... } } } } } }
Fix : limiter la profondeur et le coût des requêtes.
import depthLimit from 'graphql-depth-limit';
import costAnalysis from 'graphql-cost-analysis';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
depthLimit(5),
costAnalysis({ maximumCost: 1000 }),
],
});
3. Authentification faible
JWT — pièges classiques
// ❌ NE JAMAIS FAIRE
const decoded = jwt.decode(token);
if (decoded.admin) { /* grant admin */ }
// ✅ TOUJOURS VÉRIFIER LA SIGNATURE
const decoded = jwt.verify(token, SECRET);
Erreurs que je vois souvent :
alg: noneaccepté- Secret faible partagé
- JWT sans expiration
- Refresh token non rotatif
Rate limiting
Sans rate limit, brute-force garanti :
import rateLimit from 'express-rate-limit';
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many attempts',
});
app.post('/api/login', loginLimiter, loginHandler);
4. Mass Assignment
Un user envoie un champ non prévu (role: 'admin') et votre code fait User.create(req.body).
// ❌ MAL
const user = await User.create(req.body);
// ✅ BIEN — whitelist
const { username, email, password } = req.body;
const user = await User.create({ username, email, password });
5. CORS mal configuré
// ❌ DANGER
app.use(cors({ origin: '*' }));
// ✅ SAFE
app.use(cors({
origin: ['https://app.mondomaine.com'],
credentials: true,
}));
Checklist fin de développement
Avant de mettre une API en prod :
- Authorization vérifiée sur chaque endpoint sensible
- Requêtes paramétrées partout (zéro concaténation SQL)
- Validation des inputs (zod, joi, class-validator)
- Rate limiting sur login, register, reset password
- JWT avec secret fort, expiration courte, rotation refresh
- HTTPS obligatoire, HSTS activé
- Logs de sécurité (échecs d’auth, accès refusés)
- CORS restrictif
- Headers : CSP, X-Content-Type-Options, X-Frame-Options
- Tests avec des outils (Burp, OWASP ZAP, nuclei)
Outils que je recommande
| Outil | Usage |
|---|---|
| Burp Suite | Proxy, scanner, intruder |
| OWASP ZAP | Scanner gratuit, automatisation CI/CD |
| nuclei | Templates CVE, détection en masse |
| ffuf | Fuzzing de endpoints et params |
| graphql-playground | Introspection et tests GraphQL |
| jwt_tool | Analyse et forge de JWT |
Conclusion
90% des vulnérabilités API que je trouve viennent d’un mauvais modèle d’autorisation (BOLA) ou d’une validation d’input absente. Ces deux points couverts correctement éliminent l’essentiel du risque.
La sécurité API n’est pas un sujet “en plus” — c’est un axe à intégrer dès la conception, et à tester continuellement via des scans automatisés dans vos pipelines CI/CD.
“Une API sécurisée n’est pas une API qui ne fait rien. C’est une API qui refuse bien.”