01 - Sessions et cookies : le modèle classique
Ce que tu vas apprendre
- Comment fonctionne l'authentification par session et cookie
- Les flags de cookie et ce que chacun protégé
- Ou stocker les sessions cote serveur
- Pourquoi les sessions battent les JWT pour la plupart des apps web
Prerequisites
Avoir lu l'introduction de cette serie. Connaitre les bases du protocole HTTP.
Comment ca marche, en vrai
Le principe tient en quatre étapes. L'utilisateur envoie son login et son mot de passe. Le serveur vérifié, puis généré un identifiant de session unique (un UUID, typiquement). Cet identifiant est envoye au navigateur dans un header Set-Cookie. Ensuite, a chaque requête, le navigateur renvoie automatiquement ce cookie. Le serveur le lit, retrouve la session, et sait qui parle.
C'est le web depuis 1994. Et ca marche toujours aussi bien.
L'implementation de paltemps.fr
Sur paltemps.fr, j'utilise Elysia (Bun) avec une session en mémoire. C'est volontairement simple parce que c'est un back-office avec un seul admin :
typescript// Generation du token a chaque demarrage du serveur
const SESSION_TOKEN = crypto.randomUUID();
// Login : on verifie le mot de passe, puis on set le cookie
app.post("/api/admin/login", async ({ body, headers }) => {
const { password } = body;
if (password !== ADMIN_PASSWORD) {
recordLoginFailure(clientIp);
return new Response(JSON.stringify({ error: "Invalid" }), { status: 401 });
}
resetLoginFailures(clientIp);
return new Response(JSON.stringify({ ok: true }), {
headers: {
"Set-Cookie": `admin_token=${SESSION_TOKEN}; Path=/; HttpOnly; SameSite=Strict; Max-Age=86400`,
},
});
});
// Verification de l'auth sur chaque requete protegee
function checkAuth(headers: Record<string, string | undefined>): boolean {
const cookie = headers["cookie"] || "";
return cookie.includes(`admin_token=${SESSION_TOKEN}`);
}
Le crypto.randomUUID() généré un identifiant imprevisible a chaque démarrage. Pas de base de donnees, pas de Redis. Le token vit en mémoire. Si le serveur redemarre, la session est invalidee et l'admin doit se reconnecter. Pour un back-office mono-utilisateur, c'est suffisant.
Les flags de cookie, un par un
Le cookie Set-Cookie accepte plusieurs flags. Chacun a un rôle precis, et les oublier créé des failles.
HttpOnly empeche JavaScript d'acceder au cookie via document.cookie. Sans ce flag, une faille XSS (un script injecte dans ta page) peut lire le cookie de session et l'envoyer a un serveur malveillant. C'est la première ligne de defense contre le vol de session. Comme le dit la doc MDN sur HttpOnly : "A cookie with the HttpOnly attribute is inaccessible to the JavaScript Document.cookie API."
Secure force le navigateur a n'envoyer le cookie que sur HTTPS. En HTTP, le cookie n'est pas envoye. En production, ce flag est obligatoire. En dev local sur localhost, les navigateurs font une exception.
SameSite=Strict interdit l'envoi du cookie sur les requêtes cross-site. Si un site malveillant fait une requête vers ton API, le cookie n'est pas envoye. C'est la protection anti-CSRF la plus efficace. Il existe aussi SameSite=Lax (envoie le cookie sur les navigations top-level mais pas les requêtes AJAX cross-site) et SameSite=None (aucune protection, a éviter).
Max-Age=86400 definit la duree de vie du cookie en secondes. 86400 = 24 heures. Apres ca, le navigateur supprime le cookie. L'alternative Expires prend une date absolue. Max-Age est plus fiable parce qu'il ne depend pas de l'horloge du client.
Path=/ limite le cookie au chemin specifie. Avec /, le cookie est envoye pour toutes les requêtes. Si tu mets Path=/api, il ne sera envoye que pour les URLs commencant par /api.
Domain definit pour quels domaines le cookie est valide. Par défaut, c'est le domaine exact. Avec Domain=.example.com, le cookie est envoye a example.com et tous ses sous-domaines.
Ou stocker les sessions cote serveur
Le token dans le cookie identifié la session. Mais le serveur doit bien stocker les donnees de session quelque part.
En mémoire (une Map ou un objet). C'est ce que fait paltemps.fr. Avantages : zero dépendance, rapide. Inconvenients : perdu au redemarrage, ne fonctionne pas avec plusieurs instances du serveur (pas de partage entre processus).
Redis. La solution standard en production. Redis vit en dehors du processus Node/Bun, donc les sessions survivent aux redemarrages et sont partagees entre plusieurs instances. Un SET session:uuid data EX 86400 et c'est fait. C'est ce que j'utiliserais pour un SaaS avec des vrais utilisateurs.
Base de donnees (PostgreSQL, MySQL). Plus lent que Redis, mais pas besoin d'un service supplementaire. Correct pour les petites applications. Il faut penser a nettoyer les sessions expirees avec un cron ou un TTL.
Sessions vs JWT : mon avis
On verra les JWT dans le prochain article. Mais je pose deja ma position : pour une application web classique (frontend + backend sur le meme domaine), les sessions avec cookies sont superieures aux JWT.
Pourquoi ? Les sessions sont revocables. Tu veux deconnecter un utilisateur ? Tu supprimes sa session cote serveur. Avec un JWT, tu ne peux pas (sauf si tu maintiens une blacklist, ce qui annule l'interet du stateless).
Les cookies avec HttpOnly et SameSite=Strict sont geres nativement par le navigateur. Pas de code cote client pour stocker le token, pas de header Authorization a ajouter a chaque requête, pas de risque de mettre le token dans localStorage (accessible par XSS).
Les JWT ont leur place. Mais ce n'est pas dans une app web mono-domaine.
Navigation : Precedent : 00 - Introduction | Suivant : 02 - JWT
Sources
- MDN - Using HTTP cookies par Mozilla
- OWASP Session Management Cheat Sheet par OWASP
- RFC 6265 - HTTP State Management Mechanism par IETF
Retrouve d'autres articles techniques sur paltemps.fr.