Pourquoi la sécurité est l’affaire de tous
La sécurité web n’est pas une préoccupation réservée à des spécialistes. Chaque formulaire, chaque commentaire affiché, chaque cookie et chaque point de terminaison API est une occasion de commettre une erreur qui expose vos utilisateurs. Le coût d’une faille de sécurité est particulièrement élevé : perte de données, rupture de la confiance et risques juridiques.
La bonne nouvelle est que la plupart des attaques suivent un petit nombre de schémas et que la majorité des défenses sont bien comprises. Ce guide couvre les essentiels : le cross-site scripting, le cross-site request forgery, le CORS, l’authentification sécurisée et les headers de navigateur qui appliquent vos règles.
La règle d’or
Ne faites jamais confiance au client. Tout ce qui provient du navigateur peut être falsifié : les valeurs des formulaires, les headers, les champs cachés, les prix, les IDs utilisateurs et l’état JavaScript. La validation côté client sert à l’expérience utilisateur ; le serveur doit impérativement tout valider et autoriser à nouveau.
Ce principe unique sous-tend presque toutes les défenses. Si vous partez du principe que toute entrée est hostile et que les permissions doivent être vérifiées côté serveur, la plupart des vulnérabilités disparaissent avant même d’être écrites.
Cross-site scripting (XSS)
Le XSS est la vulnérabilité web sérieuse la plus courante. Elle survient lorsque des données contrôlées par un attaquant sont traitées comme du code. On en distingue trois types principaux : stocké (enregistré sur le serveur et servi à d’autres utilisateurs), réfléchi (renvoyé directement dans une réponse) et basé sur le DOM (introduit par le JavaScript côté client).
La défense principale est l’encodage de sortie : encodez les données en fonction du contexte dans lequel elles apparaissent. Dans le DOM, utilisez textContent plutôt que innerHTML.
// safe.js
const el = document.createElement("p");
el.textContent = userComment; // escaped
// If you truly need HTML, sanitise it
import DOMPurify from "dompurify";
el.innerHTML = DOMPurify.sanitize(userHtml);
Côté serveur, utilisez un moteur de templating qui échappe les données par défaut et ne construisez jamais de SQL ou de HTML par concaténation de chaînes. Pour les bases de données, utilisez toujours des requêtes paramétrées ou un ORM afin que les entrées ne puissent jamais devenir du SQL.
Content Security Policy
Une Content Security Policy est un en-tête de réponse qui indique au navigateur les sources qu’une page est autorisée à utiliser. Une politique stricte transforme de nombreux bugs XSS en simples erreurs inoffensives dans la console.
Content-Security-Policy:
default-src 'self';
script-src 'self';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
Commencez par Content-Security-Policy-Report-Only pour collecter les violations sans casser le site, puis passez à l’application stricte. Évitez unsafe-inline et unsafe-eval autant que possible, et utilisez des nonces ou des hashes pour les scripts inline que vous ne pouvez pas supprimer.
Cross-site request forgery (CSRF)
Le CSRF trompe le navigateur pour l’inciter à envoyer une requête authentifiée en utilisant les cookies de la victime. Si votre site utilise des sessions basées sur des cookies et qu’un endpoint modifiant l’état accepte une requête simple, la page d’un attaquant peut le déclencher.
Défenses, par couches :
- Cookies SameSite.
SameSite=LaxouStrictempêche l’envoi des cookies lors de requêtes cross-site dans les navigateurs modernes. - Jetons anti-CSRF. Un jeton par session inclus dans les formulaires et vérifié côté serveur.
- Vérification de l’origine. Rejetez les requêtes modifiant l’état dont l’en-tête
Originn’est pas votre site. - Ne jamais muter via GET. Utilisez POST, PUT, PATCH ou DELETE pour les modifications.
CORS
Le CORS est une règle du navigateur qui définit quelles origines peuvent lire les réponses de votre API. Par défaut, une page ne peut pas lire une réponse provenant d’une origine différente, à moins que le serveur ne l’autorise via Access-Control-Allow-Origin.
Deux points sont couramment mal compris :
- Le CORS protège le navigateur de l’utilisateur, pas votre serveur. D’autres serveurs peuvent appeler votre API librement ; seule l’autorisation côté serveur peut les en empêcher.
- Un
Access-Control-Allow-Origin: *permissif combiné à l’utilisation de credentials n’est pas autorisé, et refléter des origines arbitraires est dangereux.
Configurez explicitement le CORS pour les origines en lesquelles vous avez confiance, et maintenez l’authentification et l’autorisation sur le serveur.
Authentification sécurisée
L’authentification est l’étape où les erreurs sont les plus coûteuses.
- Hachez les mots de passe avec bcrypt, scrypt ou Argon2 ; n’utilisez jamais de texte brut ou de chiffrement réversible.
- Utilisez des cookies HttpOnly, Secure et SameSite pour les sessions afin que JavaScript ne puisse pas lire le jeton.
- Privilégiez les jetons à courte durée de vie avec un mécanisme de rafraîchissement, et effectuez une rotation de ceux-ci.
- Ajoutez l’authentification multi-facteur pour les comptes sensibles.
- Limitez le taux de tentatives de connexion (rate-limit) et bloquez l’accès après plusieurs échecs répétés.
- Ne placez jamais de secrets dans le code client — tout ce qui est envoyé au navigateur est public.
La comparaison ci-dessus montre le compromis entre les cookies et localStorage. Les jetons localStorage sont pratiques mais lisibles par n’importe quel script, donc une seule faille XSS permet de voler la session. Les cookies HttpOnly suppriment ce risque.
Autres essentiels
- HTTPS partout, avec HSTS pour prévenir les attaques par déclassement (downgrade attacks).
- Validez et encodez les entrées côté serveur pour chaque champ.
- Appliquez le principe du moindre privilège aux utilisateurs de base de données, aux clés API et aux rôles cloud.
- Configurez les en-têtes de sécurité : CSP, HSTS,
X-Content-Type-Options: nosniff,Referrer-Policyetframe-ancestors. - Maintenez vos dépendances à jour et auditez-les régulièrement.
- Ne divulguez aucun détail dans les messages d’erreur ou les traces de pile (stack traces) en production.
- Journalisez et surveillez les événements d’authentification et les échecs de permission.
Bonnes pratiques
- Considérez toutes les entrées client comme non fiables et validez-les côté serveur.
- Encodez les sorties selon leur contexte ; utilisez
textContentet assainissez tout HTML. - Utilisez des requêtes paramétrées pour tous les accès à la base de données.
- Stockez les sessions dans des cookies HttpOnly, Secure et SameSite.
- Ajoutez une Content Security Policy et commencez par le mode report-only.
- Utilisez SameSite ainsi que des jetons anti-CSRF pour les requêtes modifiant l’état.
- Gardez les secrets sur le serveur et renouvelez-les régulièrement.
Erreurs courantes
- Utiliser
innerHTMLavec des données utilisateur. - Placer des jetons de session dans
localStorage. - Faire confiance à la validation côté client ou aux champs de formulaire cachés.
- Construire des requêtes SQL par concaténation de chaînes.
- Considérer CORS comme un mécanisme de contrôle d’accès.
- Inclure des clés API dans le code front-end.
Et après ?
La sécurité est une pratique continue, et non une simple liste de tâches à cocher. Appuyez-vous sur le protocole HTTP, appliquez les règles de sécurité sur le serveur Node.js et gérez vos secrets en toute sécurité lors du déploiement avec Docker. Parcourez le Top 10 de l’OWASP, puis auditez de bout en bout un formulaire de votre propre application — vous trouverez presque toujours quelque chose à corriger.