Les pubs vous déplaisent ? Aller Sans pub Auj.

localStorage vs sessionStorage vs IndexedDB vs Cookies — Stockage du navigateur sans regret à 3 heures du matin

Mis à jour le

Un guide pratique pour choisir le bon stockage dans le navigateur — avec une table de comparaison, l'analyse claire du débat sur les JWT, et les méthodes spécifiques par lesquelles chaque option va ruiner votre nuit.

localStorage vs sessionStorage vs IndexedDB vs Cookies — Browser Storage Without the 3am Regret 1
ANNONCE · Supprimer ?

It’s 3am 😬. A user filed a bug: their shopping cart is empty. You open DevTools, click the Application tab, and stare at the sidebar. Où l'avez-vous mis ? La réponse correcte dépend de quelques questions que la plupart des développeurs ne se posent qu'après avoir signalé l'erreur.

Cet article n'est pas une définition. Vous pouvez trouver ces informations partout. C'est ici que nous parlons de ce qui casse, de ce qu'il faut vraiment utiliser, et pourquoi le débat sur le JWT en localStorage néglige principalement le point central.

La version courte (a.k.a. le tableau que vous marquerez)

localStoragesessionStorageIndexedDBCookies
PersistencePermanentSéance de tab uniquementPermanentConfigurable (séance ou date d'expiration)
Taille maximale5 à 10 Mo5 à 10 MoGigaoctets (quota du navigateur)Environ 4 ko par cookie
Accès au serveurNonNonNonOui — envoyé avec chaque requête
API asynchroneNon (bloque la thread principal)Non (bloque la thread principal)Oui (basé sur des promesses/événements)Non
Lisible en JavaScriptOuiOuiOuiSeulement sans le drapeau HttpOnly
Accès aux Web WorkersNonNonOuiNon
Partagé entre les ongletsOuiNon — chaque onglet est isoléOuiOui
Éviction Safari ITPAprès 7 jours sans interactionAu fermeture de l'ongletAprès 7 jours sans interactionDépend de l'attribut Expires

localStorage

Persistant, synchrone, ciblé par l'origine. Le travailleur que tout le monde utilise et qui, la moitié du temps, ne devrait pas être utilisé.

Ce qu'il est réellement

localStorage stocke des paires clé-valeur sous forme de chaînes. C'est tout. La limite de stockage est de 5 Mo dans la plupart des navigateurs, de 10 Mo dans certains (Chrome vous en donne plus). Il est ciblé par l'origine — protocole + domaine + port — donc http://example.com et https://example.com ont une sauvegarde séparée. Survit à la fermeture de l'onglet, au redémarrage du navigateur, à tout sauf si l'utilisateur efface ses données de navigateur ou si vous appelez explicitement localStorage.clear().

// Read/write is synchronous — it happens right now, on the main thread
localStorage.setItem('theme', 'dark');
const theme = localStorage.getItem('theme'); // 'dark'

// Storing objects? You're serializing manually.
localStorage.setItem('user', JSON.stringify({ id: 1, name: 'Alex' }));
const user = JSON.parse(localStorage.getItem('user'));

Où il casse

  • Quota dépassé — lance une erreur synchrone DOMException: QuotaExceededError. Si vous ne mettez pas vos écritures dans un bloc try/catch, vous découvrirez cela via un rapport d'erreur utilisateur.
  • Mode privé/incognito — les navigateurs vous donnent une localStorage fraîche, isolée, avec un quota plus strict (ou nul). Firefox a historiquement lancé des erreurs de quota immédiatement. Ne comptez jamais sur la disponibilité de localStorage sans la détecter par une fonctionnalité.
  • Safari ITP — si un utilisateur n'a pas visité votre site depuis 7 jours, Safari peut effacer localStorage. C'est un comportement documenté. Cela vous surprendra au pire moment.
  • XSS — tout ce qui est dans localStorage est lisible par n'importe quel JavaScript exécuté sur votre origine. Si un attaquant peut injecter un script, il obtient tout.

Utilisez-le pour

Préférences d'interface (mode sombre, état du panneau latéral, langue), mise en cache non sensible (heure de dernière récupération, configuration statique), tout ce qui devrait survivre à un rafraîchissement de page mais ne contient pas de jetons d'authentification ou de données personnelles. Si vous pensez à stocker un JWT ou une clé API ici — continuez à lire.

sessionStorage

Tout ce que localStorage fait, mais avec une durée de vie plus courte et une isolation cruciale qui frappe les utilisateurs constamment.

L'embûche de l'isolation par onglet

sessionStorage est par onglet, pas par navigateur. Ouvrir la même page dans un nouvel onglet vous donne une sessionStorage complètement séparée. Cela est pas évident pour les utilisateurs, et cela ne sera pas évident pour vous jusqu'à ce qu'un utilisateur signale une erreur concernant la disparition de données d'un formulaire à plusieurs étapes lorsqu'il a « accidentellement » ouvert un deuxième onglet.

L'exception : si l'utilisateur ouvre un nouvel onglet via window.open() ou en cliquant du milieu sur un lien, l'onglet nouveau reçoit une copie de la sessionStorage du parent au moment de l'ouverture. Après cela, les deux sont isolés. C'est le type d'exception qui produit une excellente question sur Stack Overflow à 2h du matin.

// Perfect for checkout flows — step data lives until the tab closes
sessionStorage.setItem('checkoutStep', '2');
sessionStorage.setItem('cartSnapshot', JSON.stringify(cart));

// Cleared automatically when the tab closes — no cleanup code needed

Utilisez-le pour

État des formulaires à plusieurs étapes, données d'un guide à usage unique, tout ce qui devrait exister pour une session et disparaître lorsque l'utilisateur ferme l'onglet. C'est utile pour les flux de paiement — vous ne voulez pas que l'état partiel du paiement reste en attente d'une session précédente. Ne l'utilisez pas si vous avez besoin de données partagées entre onglets ou pages ouvertes indépendamment.

IndexedDB

L'option mature. Asynchrone, transactionnelle, et capable de stocker des objets JavaScript réels — pas seulement des chaînes sérialisées. Elle possède aussi l'API native la plus difficile à utiliser des trois, ce qui explique pourquoi presque personne ne l'utilise directement.

Ce qu'il est réellement

IndexedDB est un stockage clé-valeur complet avec prise en charge des index et de la requête par curseur. Les limites de stockage sont généreuses — les navigateurs permettent une partie de l'espace disque, généralement des gigaoctets en pratique. Vous pouvez stocker des objets structurés, des Blobs, des ArrayBuffers et des Fichiers sans avoir à les sérialiser manuellement. Il est disponible dans les Web Workers. C'est ce que les PWAs et les applications en mode hors ligne utilisent pour stocker des données qui seraient inappropriées à garder en mémoire.

// Native IDB API — nobody writes this directly in production
const request = indexedDB.open('myDB', 1);
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  db.createObjectStore('users', { keyPath: 'id' });
};
request.onsuccess = (event) => {
  const db = event.target.result;
  const tx = db.transaction('users', 'readwrite');
  const store = tx.objectStore('users');
  store.put({ id: 1, name: 'Alex', avatar: someBlob });
};

// What you actually use — Dexie.js or the idb wrapper
import Dexie from 'dexie';
const db = new Dexie('myApp');
db.version(1).stores({ users: '++id, name, email' });
await db.users.add({ name: 'Alex', email: 'alex@example.com' });

Où il casse

  • Safari ITP — comme localStorage : après 7 jours sans interaction, Safari peut éliminer les données IndexedDB. Cela a tué plusieurs PWAs avant que Apple n'ait corrigé le comportement dans les versions plus récentes d'iOS. Si votre public cible inclut des utilisateurs de Safari sur iOS, prenez cela en compte.
  • Stockage faible sur iOS — sur iOS, le système d'exploitation peut supprimer les données IndexedDB lorsque le stockage est faible. Il ne le demande pas. Les données ne seront plus là.
  • Navigation privée — Chrome permet IndexedDB en mode incognito (avec un quota par session). Safari en mode privé avait historiquement lancé des erreurs ; le comportement varie selon la version.
  • L'API native — si vous écrivez du code brut IDB sans un wrapper, le modèle basé sur les callbacks produira des bugs que vous déboguerez pendant une après-midi. Utilisez idb ou Dexie.js.

Utilisez-le pour

Applications hors ligne, grands ensembles de données qui dépasseraient localStorage, tout ce que vous construiriez autrement sur localStorage qui heurte la limite de 5 Mo, le caching des fichiers dans les PWAs. Si vous stockez 50 documents générés par les utilisateurs localement, IndexedDB est la solution. Si vous stockez une préférence de thème utilisateur, c'est trop.

Cookies

L'ancienne des quatre. La seule que le serveur voit. La seule avec une limite de 4 Ko qui vous mordra sans aucun doute si vous tentez de stocker un JWT dedans.

Ce qui rend les cookies différents

Les cookies sont envoyés automatiquement avec chaque requête correspondante. C'est à la fois une fonctionnalité et un problème. Cela signifie que votre cookie de session atteint votre serveur sans aucune intervention JavaScript — et cela signifie aussi que chaque requête à api.example.com porte un surcoût de cookie, qu'il vous plaise ou non.

Les attributs qui comptent vraiment :

  • HttpOnly — JavaScript ne peut pas lire ce cookie. Les attaques XSS ne peuvent pas l'exfiltrer. C'est une condition de base pour les cookies de session.
  • Sécurisé — envoyé uniquement sur HTTPS. Sans cela sur un site en production, vous envoyez des cookies d'authentification sur HTTP. Ne le faites pas.
  • SameSite=Strict — le cookie n'est envoyé que lorsque la requête provient de votre propre domaine. Protection efficace contre le CSRF, mais elle interrompt les flux d'OAuth. SameSite=Lax est un compromis raisonnable pour la plupart des applications.
  • Expires / Max-Age — sans ces attributs, c'est un cookie de session qui se termine lorsque le navigateur se ferme. Fixez une date d'expiration explicite pour le comportement « se souvenir de moi ».
// Setting a cookie from JavaScript (no HttpOnly, obviously)
document.cookie = "theme=dark; Path=/; Max-Age=31536000; Secure; SameSite=Lax";

// Server-side (Node.js + Express) — where the real power is
res.cookie('sessionId', token, {
  httpOnly: true,   // JS cannot read this
  secure: true,     // HTTPS only
  sameSite: 'lax',  // balanced CSRF protection
  maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days in ms
});

Si vous déboguez un string de cookie complexe, IO Tools’ Cookie Parser le décompose en paires clé-valeur lisible — utile lorsque vous regardez une en-tête de 400 caractères Set-Cookie et que vous tentez de déterminer quel attribut est erroné.

Où il casse

  • Limite de 4 Ko — c'est la taille totale, incluant le nom + la valeur + tous les attributs. Un JWT typique mesure 400 à 800 octets. Un gros JWT avec de nombreux détails mesure 1 à 2 Ko. Vous n'avez pas beaucoup d'espace.
  • SameSite=Strict annule OAuth — lorsque votre fournisseur d'identité redirige vers votre application après la connexion, il s'agit d'une requête de site différent. SameSite=Strict signifie que votre cookie de session ne sera pas envoyé. Utilisez Lax pour tout ce qui passe par des flux d'OAuth.
  • Ordre des cookies et correspondance du chemin — lorsque plusieurs cookies ont des chemins superposés, le navigateur les envoie dans un ordre indéterminé. Ne comptez pas sur la priorité.
  • Dépréciation des cookies tiers — Chrome élimine les cookies tiers. Les dépendances de cookies entre sites sont une responsabilité à moyen terme.

Le débat sur le JWT en localStorage (la surface réelle d'attaque)

Cela se produit dans chaque discussion sur l'authentification, et la plupart des personnes qui en parlent se parlent en passant.

L'inquiétude concernant le stockage de JWT dans localStorage : si votre site a une vulnérabilité XSS, un script de l'attaquant peut lire directement le jeton et l'exfiltrer. L'attaquant obtient maintenant un jeton d'authentification valide qu'il peut utiliser n'importe où, sur n'importe quel appareil, jusqu'à son expiration.

L'argument en faveur des cookies HttpOnly : JavaScript ne peut pas lire ce cookie, donc XSS ne peut pas l'exfiltrer. La session reste utilisable dans les requêtes (l'attaquant peut faire des requêtes depuis le navigateur de la victime via XSS), mais il ne peut pas voler le jeton pour l'utiliser ailleurs. Cela limite la surface d'impact.

L'approche honnête : le problème fondamental est XSS, pas la localisation du stockage. Si vous avez une vulnérabilité XSS, un cookie HttpOnly est significativement meilleur — l'attaquant ne peut pas emporter le jeton hors site. Mais corriger XSS est un objectif plus pertinent, réalisable avec une politique de sécurité stricte, sans scripts tiers non contrôlés, et avec une échappement correct des sorties.

Si vous construisez une SPA avec une politique de sécurité stricte et sans scripts tiers que vous ne contrôlez pas, localStorage est probablement acceptable pour les JWT. Si vous gérez un site avec Google Tag Manager, des pixels publicitaires et une douzaine de dépendances npm, les cookies avec HttpOnly sont beaucoup plus sûrs parce que votre surface d'attaque XSS est plus grande que vous ne le pensez.

Lorsque vous déboguez des problèmes liés aux JWT, le JWT Decoder sur IO Tools est utile — collez le jeton et voyez le payload et la date d'expiration sans écrire de code. Le Vérificateur d'expiration JWT est utile pour confirmer si un jeton est encore valide lorsque vous tracez une cascade de 401.

Diagramme de décision

Avant d'ouvrir une page Stack Overflow à 3h du matin, passez par ce diagramme :

  1. Le serveur a besoin de le lire ? → Cookie. Fin de la discussion.
  2. Est-il plus grand que 5 Mo ou avez-vous besoin de requêtes ? → IndexedDB.
  3. Doit-il disparaître lors de la fermeture de l'onglet ? → sessionStorage.
  4. Tout le reste → localStorage, avec une prudence appropriée sur ce que vous stockez.

La seule chose que tout le monde fait mal

Les API de stockage ne sont pas fiables pour tous les utilisateurs à tout moment. Elles peuvent être effacées par le navigateur, l'OS, les paramètres de confidentialité ou l'utilisateur. Toute architecture qui traite le stockage client comme une source de vérité — plutôt qu'un cache — échouera un jour.

Le modèle qui tient : traiter le stockage du navigateur comme une optimisation de performance par rapport à votre source de vérité réelle (le serveur). Stockez de manière agressive, mais concevez votre application pour se rétablir de manière fluide lorsque le cache est vide. Les sessions de débogage à 3h sont presque jamais liées à l'API de stockage utilisée — elles sont liées à l'assomption que les données seraient là quand elles n'étaient pas là.

Envie d'une expérience sans pub ? Passez à la version sans pub

Installez nos extensions

Ajoutez des outils IO à votre navigateur préféré pour un accès instantané et une recherche plus rapide

Sur Extension Chrome Sur Extension de bord Sur Extension Firefox Sur Extension de l'opéra

Le Tableau de Bord Est Arrivé !

Tableau de Bord est une façon amusante de suivre vos jeux, toutes les données sont stockées dans votre navigateur. D'autres fonctionnalités arrivent bientôt !

ANNONCE · Supprimer ?
ANNONCE · Supprimer ?
ANNONCE · Supprimer ?

Coin des nouvelles avec points forts techniques

Impliquez-vous

Aidez-nous à continuer à fournir des outils gratuits et précieux

Offre-moi un café
ANNONCE · Supprimer ?