Skip to content

Sécurité fr FR

rocambille edited this page Apr 28, 2026 · 4 revisions

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).

Les concepts de sécurité

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.

Implémentation dans StartER

StartER intègre plusieurs middlewares de sécurité dans server.ts, activés avant toute route :

Helmet

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.

Rate limiting

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

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).

Compression

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());
}

CSRF (Cross-Site Request Forgery)

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.

Génération d'un jeton côté client

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>

Vérification côté serveur

Le serveur :

  1. intercepte les requêtes POST/PUT/PATCH/DELETE,
  2. lit le cookie __Host-x-csrf-token,
  3. compare avec l'en-tête x-csrf-token,
  4. répond avec le statut 401 en 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.

Protection contre le XSS

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 dangerouslySetInnerHTML nulle 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.

Conseils supplémentaires

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.

Cookies, authentification et intégrité

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 :

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.

Jeton d'accès (JWT)

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).

Bonnes pratiques et cas d'usage

Pas de cookies cross-site

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.

API stateless

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.

Pas d'upload direct non contrôlé

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...).

En-têtes HTTP

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 header Referer

Gestion des erreurs et messages de sécurité

Les erreurs renvoyées par le back sont volontairement sobres :

  • 400 pour les requêtes mal formées,
  • 401 pour les problèmes d'authentification,
  • 403 pour les problèmes d'autorisation,
  • 500 pour 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.

Recommandations additionnelles

Journalisation

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.

Surveillance des mutations

Les requêtes mutatives (PUT, POST, PATCH, DELETE) devraient être systématiquement loggées avec :

  • timestamp,
  • utilisateur authentifié,
  • endpoint appelé.

Rotation des clés JWT

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.

Voir aussi

Clone this wiki locally