<link rel="stylesheet" href="/assets/fonts/jetbrains-mono/jetbrains-mono.css" />
All posts

Bonnes pratiques combinées pour applications Web modernes : Sécurité, cache et Websocket

Les meilleures pratiques individuelles en matière de performances Web et de sécurité sont largement documentées, mais rarement sont expliqués ensemble comme un système cohérent : TLS partout, cookies de session configuré correctement, pas de JWT dans localStorage, mise en cache agressive uniquement là où cela est sûr, Révocation réelle des sessions et des WebSockets, limitation du débit sur les canaux temps réel et surveillance des connexions tous ces signaux. Ce guide couvre dix pratiques qui, appliquées ensemble, réduisent véritablement la surface d'attaque sans sacrifier les performances perçues par les utilisateurs.

Chaque section comprend des configurations concrètes (Nginx, Express, NestJS, WebSocket) et les vrais compromis de chaque choix — parce que chaque pratique a un coût, et les appliquer sans le comprendre conduit à des configurations un culte du cargo qui ne protège vraiment rien.

1. HTTPS/WSS requis

TLS n'est pas négociable pour HTTP ou WebSocket : sans wss://, un WebSocket en texte brut expose les jetons de session et les charges utiles des applications à toute personne se trouvant sur le même réseau. Configurez TLS 1.2 comme minimum (1,3 si possible), HSTS avec preload et agrafage OCSP pour éviter la latence une vérification du certificat en temps réel à chaque poignée de main.

# nginx.conf — TLS 1.2/1.3, HSTS, OCSP stapling
server {
  listen 443 ssl;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_stapling on;
  ssl_stapling_verify on;
  add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

  location /ws/ {
    proxy_pass http://backend_ws;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

2. Cookies de session : HttpOnly, Secure, SameSite

HttpOnly empêche JavaScript de lire le cookie (atténue le vol via XSS), Secure l'envoie uniquement via HTTPS, SameSite contrôle l'envoi entre sites : Strict pour une protection CSRF maximale (mais interrompt les flux avec des redirections externes), Lax comme compromis raisonnable pour la plupart des applications, Aucun uniquement si le cookie doit être véritablement intersite — et dans ce cas, il nécessite Sécurisé obligatoire.

// Express/NestJS — cookie di sessione configurato correttamente
res.cookie('session_id', sessionToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  maxAge: 15 * 60 * 1000, // 15 minuti, coerente con la strategia di refresh
});

3. Jamais JWT dans localStorage

localStorage est lisible par n'importe quel script exécuté sur la page : un seul XSS (même dans une dépendance tierce, pas dans votre code) exfiltre le token sans avoir besoin d'aucune interaction utilisateur. L'alternative sécurisée est le HttpOnly cookie pour le jeton lui-même, combiné avec le modèle double submit cookie pour la protection CSRF lorsque vous avez également besoin d'un mécanisme apatride.

// Refresh token in HttpOnly cookie, access token short-lived in memoria (mai in storage persistente)
app.post('/auth/refresh', (req, res) => {
  const refreshToken = req.cookies.refresh_token; // HttpOnly, non leggibile da JS
  const newAccessToken = issueAccessToken(verifyRefreshToken(refreshToken));
  res.json({ accessToken: newAccessToken }); // vive solo in memoria lato client, mai in localStorage
});

4. Cache d'actifs statiques avec empreintes digitales

Un actif avec un hachage dans le nom de fichier (main.a1b2c3d4.js) peut être mis en cache pendant une année entière avec immuable, car chaque modification du contenu génère automatiquement un nom de fichier différent : l'invalidation du cache ne nécessite jamais de purge manuelle.

# Angular CLI genera automaticamente asset con hash nel filename in build di produzione
ng build --configuration production
# Output: main.a1b2c3d4.js, styles.e5f6a7b8.css
# Header cache per asset fingerprinted
location ~* \.[0-9a-f]{8}\.(js|css)$ {
  add_header Cache-Control "public, max-age=31536000, immutable";
}

5. Ne pas mettre en cache les réponses sensibles

Toute réponse API contenant des données personnelles ou de session utilisateur doit indiquer explicitement Cache-Control : no-store (jamais enregistré, même pas dans la mémoire temporaire) ou private (mise en cache uniquement depuis le navigateur de l'utilisateur, jamais depuis un cache/CDN partagé). Si la réponse varie en basé sur un en-tête comme Authorization ou Accept-Language, déclarez-le avec Vary pour empêcher un cache intermédiaire de fournir une mauvaise réponse utilisateur.

// NestJS — risposta con dati personali, mai cacheabile
@Get('me')
getProfile(@Res({ passthrough: true }) res: Response) {
  res.setHeader('Cache-Control', 'no-store');
  return this.userService.getCurrentProfile();
}

6. CDN avec s-maxage Séparé du navigateur

s-maxage contrôle la durée pendant laquelle le CDN (cache partagé) conserve la réponse, indépendamment du fait que max-age contrôle le navigateur de l'utilisateur — cela permet diffusez du contenu quasi statique depuis la périphérie pendant quelques minutes tout en gardant votre navigateur à jour en quelques minutes secondes, ou vice versa.

# Header: CDN cachea 5 minuti, browser solo 30 secondi
Cache-Control: public, s-maxage=300, max-age=30

# Purge mirata di un singolo path sulla CDN dopo un deploy
curl -X POST "https://api.cdn-provider.com/purge" -H "Authorization: Bearer $CDN_TOKEN" -d '{"path":"/api/products"}'

7. Actualiser et révoquer les jetons pour les sessions et les WebSockets

Un jeton d'accès de courte durée (10 à 15 minutes) et un jeton d'actualisation HttpOnly de plus longue durée limitent la fenêtre endommagée en cas de vol. La vraie révocation nécessite un état côté serveur (une liste noire ou un compteur de version par utilisateur) : un JWT purement apatride ne peut pas être invalidé avant son expiration naturelle. Pour les WebSockets, la révocation doit fermer activement le connexion, ne vous contentez pas de rejeter les requêtes HTTP suivantes.

// Revoca sessione: invalida il refresh token e chiude ogni WebSocket associato all'utente
async function revokeSession(userId: string) {
  await sessionStore.incrementTokenVersion(userId); // invalida tutti i JWT emessi prima di ora
  for (const socket of wsConnections.getByUserId(userId)) {
    socket.close(4001, 'session_revoked');
  }
}

8. Limitation de débit et limite de taille sur WebSocket

Sans limites, un seul client peut saturer le serveur avec des messages ou des charges utiles à haut débit énorme - est la forme la plus simple de DoS d'application à exécuter sur un canal non WebSocket protégé.

// ws — rate limiting e size limit per connessione
const wss = new WebSocketServer({ maxPayload: 64 * 1024 }); // 64KB max per messaggio

wss.on('connection', (socket) => {
  let messageCount = 0;
  const resetInterval = setInterval(() => (messageCount = 0), 1000);

  socket.on('message', (data) => {
    if (++messageCount > 20) return socket.close(4008, 'rate_limit_exceeded'); // max 20 msg/sec
    handleMessage(data);
  });

  socket.on('close', () => clearInterval(resetInterval));
});

9. Surveillance

Les métriques à suivre couvrent quatre domaines : connexions (WebSockets actifs, taux reconnexion), latence/débit (temps de réponse API, messages/seconde), cache (taux de réussite/échec pour le CDN, TTL effectif observé) et auth (taux d'échec du jeton d'actualisation, sessions révoquées). Prometheus + Grafana couvrent bien les métriques chiffres et tableaux de bord en temps réel ; ELK (ou équivalent) reste le mieux adapté à la journalisation des applications recherches structurées et ponctuelles sur les événements de sécurité.

// Prometheus — contatore custom per connessioni WebSocket attive
const wsConnectionsGauge = new client.Gauge({ name: 'ws_active_connections', help: 'Connessioni WebSocket attive' });
wss.on('connection', () => {
  wsConnectionsGauge.inc();
  return () => wsConnectionsGauge.dec();
});

10. Test d'accessibilité et de confidentialité

Vérifiez qu'aucun cookie ou en-tête mis en cache n'expose par inadvertance des données personnelles (un Set-Cookie avec un e-mail en texte brut dans le nom, une réponse mise en cache publiquement qui contient le nom d'utilisateur). Tests automatisés (axe-core pour a11y, scanner d'en-tête de sécurité IC) ils couvrent des cas objectifs ; un examen manuel périodique des en-têtes Cache-Control sur les itinéraires avec des données personnelles restent nécessaires car un nouveau point de terminaison peut facilement oublier le bon en-tête.

Liste de contrôle opérationnel rapide

  • Haute priorité : HTTPS/WSS partout, cookie de session HttpOnly+Secure+SameSite, supprimez tout JWT de localStorage.
  • Priorité moyenne : empreintes digitales d'actifs statiques, no-store en-tête sur les réponses avec des données personnelles, limitation de débit WebSocket.
  • Priorité continue : surveillance de connexion/cache/authentification, révision périodique des en-têtes de cache sur les nouvelles routes.

Forfait 30/60/90 jours

Jours 1-30

  • HTTPS/WSS forcé partout, HSTS actif — KPI : 0 point de terminaison accessible en texte clair.
  • Cookies de session migrés vers HttpOnly+Secure+SameSite — KPI : 0 jeton lu à partir de JavaScript côté client.

Jours 31-60

  • Limitation de débit active sur tous les canaux WebSocket — KPI : 0 connexion capable de dépasser les limites fixées.
  • Correction des en-têtes de mise en cache sur toutes les routes contenant des données personnelles — KPI : audit complet, 0 réponse sensible pouvant être mise en cache publiquement.

Jours 61-90

  • Tableau de bord de surveillance complet (connexions, cache, authentification) — KPI : alertes configurées sur chaque métrique critique.
  • Révocation des jetons et WebSocket testée de bout en bout — KPI : temps de révocation effectif inférieur à 2 secondes.

FAQ

SameSite=Strict ou Lax pour un cookie de session ?

Lax est le bon compromis pour la plupart des applications ; Strict uniquement si vous n'avez pas de flux avec des redirections de domaines externes vers l'application.

Pourquoi ne pas simplement utiliser sessionStorage au lieu de localStorage pour le JWT ?

Les deux sont également lisibles par JavaScript et donc vulnérables à XSS : la différence réside uniquement dans la durabilité, pas dans la sécurité.

maxage fonctionne-t-il sans CDN configuré ?

Non, il est ignoré par les navigateurs : il n'affecte que les caches partagés conformes tels que les CDN ou les proxy inverses qui le prennent explicitement en charge.

Comment puis-je révoquer un JWT avant son expiration ?

Avec état côté serveur (liste noire ou version de jeton par utilisateur), car un JWT pur sans état ne peut pas être invalidé avant l'expiration naturelle.

Quelle est une limite raisonnable de messages par seconde pour un WebSocket ?

Cela dépend du cas d'utilisation, mais 10 à 30 messages/seconde par connexion constituent un point de départ raisonnable pour la plupart des applications en temps réel.

L'agrafage OCSP est-il obligatoire ?

Pas obligatoire mais fortement recommandé : réduit la latence de la prise de contact TLS en empêchant le client de contacter l'autorité de certification séparément.

Comment empêcher un CDN de fournir une réponse sensible au mauvais client ?

Avec Cache-Control : privé ou no-store sur les réponses personnelles, jamais public lorsque les données varient selon l'utilisateur.

Devez-vous également surveiller les échecs d'actualisation des jetons ?

Oui, une augmentation soudaine des échecs d'actualisation est souvent le premier signe d'une attaque en cours ou d'un bug d'expiration de jeton.

Erreurs courantes à éviter

  • WebSocket en texte clair (ws://) derrière une interface HTTPS : le navigateur l'autorise uniquement si le WebSocket se trouve sur la même origine non sécurisée, mais expose toujours les données en texte brut au réseau.
  • Cookies sans Secure en développement également laissés en production : souvent copiés à partir d'une configuration de test qui n'a jamais été mise à jour.
  • JWT dans localStorage "temporairement" pendant le développement : devient presque toujours permanent car il "fonctionne" et personne ne le revoit.
  • Cache-Control absent par défaut sur les nouvelles routes API : sans en-tête explicite, le comportement de la mise en cache dépend du client et n'est pas garanti.
  • s-maxage et max-age identiques : élimine l'avantage de pouvoir invalider le cache du navigateur plus rapidement que le cache périphérique.
  • Aucune taille maximale sur les messages WebSocket : Un seul client peut envoyer d'énormes charges utiles et saturer la mémoire du serveur.
  • Révoquer qui bloque uniquement les futures requêtes HTTP : laissez les connexions WebSocket existantes actives avec le même jeton compromis.
  • Pas de surveillance des succès/échecs du cache : un taux de réussite en baisse signale souvent une régression des en-têtes de cache passée inaperçue.

Réponses rapides

Pourquoi HTTPS est-il également requis pour les WebSockets ? Un WebSocket en texte brut (ws://) expose les jetons de session et les charges utiles d'application à toute personne se trouvant sur le même réseau ; wss:// chiffre l'intégralité de la connexion exactement comme le fait HTTPS pour les requêtes HTTP traditionnelles.

Qu'est-ce que l'indicateur SameSite d'un cookie ? Contrôle si un cookie est envoyé dans les requêtes intersites : Strict bloque l'envoi entre sites, Lax l'autorise uniquement pour la navigation de niveau supérieur, Aucun autorise toujours mais nécessite Secure.

Pourquoi ne pas enregistrer un JWT dans localStorage ? Parce qu'il est lisible par n'importe quel script JavaScript exécuté sur la page : un seul XSS, même dans une bibliothèque tierce, permet d'exfiltrer le token sans aucune interaction de l'utilisateur.

Que signifie Cache-Control : immuable ? Indique au navigateur que le contenu de cette URL ne changera jamais tant que l'URL reste la même, évitant même une demande de validation conditionnelle pendant la période de cache.

Comment révoquer une session avec des WebSockets actifs ? En invalidant l'état côté serveur du jeton (liste noire ou version) et en fermant activement chaque connexion WebSocket associée à cet utilisateur, pas seulement en rejetant les nouvelles requêtes HTTP.

Quelle est la différence entre max-age et s-maxage ? max-age vérifie le cache du navigateur de l'utilisateur, s-maxage vérifie les caches partagés tels que CDN et proxy inverse, et lorsqu'il est présent, il est prioritaire plus de max-age pour ces caches.

Comment vérifier

  • Vérifiez les redirections TLS et HTTPS : curl -I https://votre-domaine.com et vérifiez l'en-tête Strict-Transport-Security.
  • Inspectez le certificat et le protocole TLS : openssl s_client -connect your-domain.com:443 -tls1_2.
  • Vérifiez les en-têtes de cache sur les actifs : curl -I https://your-domain.com/main.a1b2c3d4.js, vérifiez immutable et âge maximum.
  • Vérifiez que les réponses sensibles ne peuvent pas être mises en cache : curl -I https://your-domain.com/api/me, cochez no-store.
  • Simulez la charge sur WebSocket pour valider la limitation de débit : npx artillerie quick --count 100 -n 20 wss://your-domain.com/ws.
  • Testez la révocation de bout en bout : révoquez une session via l'API et vérifiez que le WebSocket associé se ferme en quelques secondes.

Conclusion

Aucune de ces dix pratiques n’est suffisante à elle seule : TLS sans cookies correctement configurés laisse toujours la session vulnérable, mise en cache agressive sans distinguer les réponses publiques des private expose les données personnelles, et la limitation du débit sans surveillance ne vous permet pas de savoir si c'est le cas ça marche vraiment. Appliqués ensemble, avec un plan d'adoption priorisé (HTTPS et cookies en premier, mise en cache et limitation du débit ensuite, surveillance continue toujours), constituent une base de sécurité et de performance cohérent pour toute application Web moderne avec des composants en temps réel.

Voulez-vous une liste de contrôle imprimable ou une évaluation de votre configuration de sécurité application ? Demander un audit technique : en quelques heures d'analyse il est possible d'identifier le lacunes prioritaires sur les cookies, la mise en cache, les WebSockets et la surveillance.

💬 Notes des lecteurs

0 notes

Écrire une note

Partagez votre avis, une suggestion ou un compliment

Dernières notes

Aucune note pour le moment. Soyez le premier à commenter !