-
Notifications
You must be signed in to change notification settings - Fork 7
Sécurité fr FR
Résumé : StartER met en place les protections essentielles pour sécuriser votre application web contre les failles courantes (CSRF, XSS, attaques par force brute).
La sécurité web est un domaine vaste, et il est normal de se sentir dépassé au début. L'objectif de StartER n'est pas de tout couvrir, mais de vous équiper avec un socle solide et de vous aider à comprendre pourquoi ces protections existent.
Tip
Si vous découvrez ces concepts, lisez cette page dans l'ordre : chaque section s'appuie sur les précédentes. Les termes techniques sont définis dans le Glossaire technique.
StartER intègre plusieurs middlewares de sécurité dans server.ts, activés avant toute route :
Helmet configure automatiquement des en-têtes HTTP de sécurité comme Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, et Content-Security-Policy.
app.use(
helmet({
contentSecurityPolicy: isProduction,
}),
);Note
La Content-Security-Policy est désactivée en développement car le serveur Vite utilise des WebSockets et l'évaluation dynamique de modules, ce qui est bloqué par la CSP par défaut de Helmet. En production, la CSP est activée avec les valeurs par défaut de Helmet.
express-rate-limit protège contre les attaques par force brute et les abus :
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
limit: 100, // max 100 requêtes par fenêtre
});
app.use(limiter);Cette configuration est volontairement simple et doit être ajustée selon les besoins du déploiement.
cookie-parser est enregistré dans src/express/routes.ts. Il analyse les cookies des requêtes entrantes et les rend accessibles via req.cookies. Ce middleware est indispensable pour l'authentification (cookie __Host-auth) et la validation CSRF (cookie __Host-x-csrf-token).
En production uniquement, compression est activée pour réduire la taille des réponses HTTP :
if (isProduction) {
const compression = (await import("compression")).default;
app.use(compression());
}Dans l'architecture de StartER :
- l'API est stateless,
- le serveur ne maintient aucune session côté back,
- StartER ne configure pas explicitement CORS. Par défaut, aucune requête cross-site n'est autorisée, car le serveur ne renvoie aucun
Access-Control-Allow-Origin. - le front est servi sur le même domaine que le back : c'est le seul domaine autorisé à communiquer avec l'API,
- les cookies sont en
SameSite=strict.
Cela bloque déjà la majorité des attaques CSRF classiques, puisqu'un site tiers ne peut ni envoyer les cookies stricts, ni interagir avec l'API.
StartER utilise une protection additionnelle, notamment pour les requêtes mutatives (POST, PUT, PATCH, DELETE) : le pattern Client-Side Double-Submit.
Tip
Le pattern est particulièrement bien expliqué dans la FAQ du package csrf-csrf, avec d'autres éléments de réponse sur les attaques CSRF, la nécessité ou non de mettre en place des protections...
Le pattern est également mentionné entre autres dans la documentation de Symfony.
Pour compléter sur le sujet, nous vous recommandons de lire la documentation de l'OWASP.
Le front génère un jeton CSRF par l'utilitaire csrfToken() dans src/react/components/utils :
- le jeton est stocké dans un cookie
__Host-x-csrf-token(voir la documentation de l'OWASP sur le préfixe__Host-), - il expirera au bout de 30 secondes,
- mais chaque utilisation prolonge son expiration, reproduisant le comportement d'un cookie de session côté client,
- aucun stockage serveur n'est nécessaire.
Ce mécanisme permet de dire "la session CSRF est active tant qu'il y a de l'activité", tout en ayant un timeout explicite pour éviter les jetons périmés conservés trop longtemps.
Chaque requête mutative inclut un en-tête contenant la même valeur que celle stockée dans le cookie : c'est le "double-submit".
X-CSRF-Token: <token>
Le serveur :
- intercepte les requêtes POST/PUT/PATCH/DELETE,
- lit le cookie
__Host-x-csrf-token, - compare avec l'en-tête
x-csrf-token, - répond avec le statut
401en cas d'absence du header ou d'incohérence entre le header et le cookie.
Le serveur reste entièrement stateless : il compare simplement deux valeurs transmises par le client, sans maintenir de session ni stocker de token côté back.
Cette approche est acceptable tant que :
- L'API n'est pas exposée à des tiers (pas de cross-site autorisé).
-
SameSite=strictélimine la possibilité qu'un site tiers envoie les cookies nécessaires.
L'essentiel des protections XSS repose sur le front :
- aucune interpolation non sécurisée n'est faite dans le DOM,
- React empêche par défaut les injections HTML,
- StartER n'utilise
dangerouslySetInnerHTMLnulle part.
Du côté backend :
- les réponses JSON sont servies avec un
Content-Type: application/json, ce qui empêche leur interprétation comme du HTML ou du JavaScript exécutable, - toutes les données stockées en base sont traitées comme du contenu opaque côté front.
Pour les projets qui souhaitent aller plus loin :
- personnaliser la Content-Security-Policy (CSP) activée par Helmet en production,
- bloquer toute inline-script,
- n'autoriser que des sources explicites pour les scripts et styles.
Tip
La CSP par défaut de Helmet est un bon point de départ. Consultez la documentation de Helmet pour personnaliser les directives selon vos besoins.
StartER utilise deux cookies principaux :
-
__Host-auth: jeton JWT signé, portant l'identité utilisateur -
__Host-x-csrf-token: jeton CSRF éphémère renouvelé côté client
Les deux sont :
- en
SameSite=strict - en
Path=/ - préfixés avec
__Host-(voir la documentation de l'OWASP sur les préfixes de cookies)
Le cookie __Host-auth est en HttpOnly, ce qui le rend inaccessible par du code JS côté client. Ce n'est pas le cas pour le jeton CSRF qui est écrit côté client.
Le JWT stocké en cookie ne représente pas une session mais une attestation signée. Sa durée de vie doit rester courte, afin de limiter la fenêtre d'usage d'un jeton compromis.
Le JWT n'est pas chiffré, uniquement signé. Il contient des données qui sont lisibles par le client, mais protégées contre la modification.
Points importants :
- le serveur ne stocke pas d'état de session,
- la révocation se fait par expiration ou demande explicite au serveur de supprimer le cookie (logout).
StartER ne sert aucun contenu depuis un domaine tiers, ni depuis un sous-domaine différent. Cela réduit drastiquement les risques CSRF ainsi que les attaques liées aux requêtes provenant d'autres sites.
L'absence de session côté serveur limite les risques de :
- session fixation,
- cleanup incomplet,
- dépassement de mémoire pour suivi de sessions longues.
Par défaut, StartER ne traite pas l'upload de fichiers. Si ajouté, il doit être filtré, stocké hors du document root et validé (type, taille...).
Helmet configure automatiquement les en-têtes de sécurité essentiels. En complément, le serveur de production (Caddy, Nginx ou autre) peut être configuré pour renforcer ces en-têtes :
-
Strict-Transport-Security: force l'utilisation de HTTPS -
X-Content-Type-Options: nosniff: empêche le navigateur de deviner le type MIME -
X-Frame-Options: DENY: bloque l'intégration de la page dans une iframe -
Referrer-Policy: strict-origin-when-cross-origin: limite les informations transmises via le headerReferer
Les erreurs renvoyées par le back sont volontairement sobres :
-
400pour les requêtes mal formées, -
401pour les problèmes d'authentification, -
403pour les problèmes d'autorisation, -
500pour les erreurs non gérées côté serveur, - pas de message détaillé exposant la nature de la défaillance (token manquant, signature invalide, cookie expiré...).
Le front affiche ensuite des messages clairs pour l'utilisateur sans révéler d'informations sensibles.
Il est recommandé de logger côté serveur les erreurs 401/403, ce qui inclut :
- les échecs d'authentification,
- les tentatives d'accès non authentifiées,
- les accès interdits.
Les requêtes mutatives (PUT, POST, PATCH, DELETE) devraient être systématiquement loggées avec :
- timestamp,
- utilisateur authentifié,
- endpoint appelé.
Un mécanisme périodique (mensuel / trimestriel) est recommandé pour prévenir :
- la fuite éventuelle d'une clé,
- la persistance de tokens anciens signés avec une clé obsolète.
Co-création IA
Bien démarrer
Explications
Guides
Référence
Aller plus loin