Chapitre 915 minDébutant
localStorage et sessionStorage : stocker des données dans le navigateur
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- distinguer
localStorage,sessionStorageet les cookies ; - enregistrer et relire des objets en JSON ;
- gérer un stockage absent, plein ou corrompu ;
- dire ce qu'il ne faut jamais stocker dans le navigateur, jetons compris.
4 exercices corrigés
localStorage est un espace de stockage clé-valeur du navigateur, propre à chaque site, où une page peut enregistrer du texte qui reste disponible à la visite suivante ; sessionStorage fonctionne pareil mais s'efface quand on ferme l'onglet. Ce chapitre montre comment y ranger des objets en JSON, comment gérer un stockage absent ou corrompu, et surtout ce qu'il ne faut jamais y mettre : les jetons d'authentification et les données sensibles.
localStorage, sessionStorage et cookies : les différences
localStorage | sessionStorage | Cookie | |
|---|---|---|---|
| Durée de vie | Jusqu'à suppression (par le code ou l'utilisateur) | Jusqu'à la fermeture de l'onglet | Date d'expiration choisie par le serveur ou le script |
| Partagé entre | Tous les onglets du même site | Un seul onglet | Tous les onglets ; envoyé au serveur |
| Envoyé au serveur | Non | Non | Oui, à chaque requête vers son domaine |
| Taille | Environ 5 Mo par site | Environ 5 Mo par site | Environ 4 Ko par cookie |
| Lisible par JavaScript | Oui, toujours | Oui, toujours | Oui, sauf s'il est marqué HttpOnly |
| Type de valeur | Chaînes uniquement | Chaînes uniquement | Chaînes uniquement |
« Le même site » veut dire la même origine : même protocole, même domaine, même port. http://localhost:5173 et http://localhost:8000 sont deux origines différentes, avec deux stockages séparés. C'est pour cela que tes données semblent « disparaître » quand tu changes de serveur de développement.
Utilise localStorage pour ce qui doit survivre à une fermeture du navigateur (les préférences d'affichage, un brouillon, le stock de Gestock dans sa version sans serveur), sessionStorage pour ce qui ne concerne que l'onglet en cours (l'étape atteinte dans un formulaire en plusieurs pages).
Les méthodes : setItem, getItem, removeItem
localStorage.setItem('gestock.theme', 'sombre'); // enregistre
localStorage.getItem('gestock.theme'); // 'sombre'
localStorage.getItem('cle-inexistante'); // null
localStorage.removeItem('gestock.theme'); // supprime une clé
localStorage.clear(); // supprime TOUT le stockage du site
localStorage.length; // nombre de clés
sessionStorage a exactement les mêmes méthodes. Préfixe tes clés avec le nom de l'application (gestock.pieces, gestock.theme) : plusieurs scripts d'un même site partagent le stockage, et des clés génériques comme data finissent par se marcher dessus.
Stocker un objet ou un tableau : JSON obligatoire
Le stockage ne garde que des chaînes. Tout le reste est converti en texte, souvent de façon inutilisable :
localStorage.setItem('quantite', 12);
typeof localStorage.getItem('quantite'); // 'string' : '12'
localStorage.setItem('piece', { ref: 'SSD-512' });
localStorage.getItem('piece'); // '[object Object]' : les données sont perdues
Pour un objet ou un tableau, passe par JSON (chapitre 6) : JSON.stringify à l'écriture, JSON.parse à la lecture.
const pieces = [{ ref: 'SSD-512', quantite: 12 }, { ref: 'RAM-16', quantite: 3 }];
localStorage.setItem('gestock.pieces', JSON.stringify(pieces));
const relues = JSON.parse(localStorage.getItem('gestock.pieces'));
relues[1].quantite; // 3 : un vrai nombre
Gérer les erreurs : stockage absent, plein ou corrompu
Lire le stockage peut mal se passer, et ton application doit continuer à fonctionner :
| Situation | Ce qui se passe |
|---|---|
| Première visite | getItem renvoie null ; JSON.parse(null) renvoie null |
| Données modifiées à la main ou par une ancienne version de l'application | JSON.parse lève une SyntaxError, ou renvoie autre chose que ce que tu attends |
| Stockage plein (environ 5 Mo) | setItem lève une erreur QuotaExceededError |
| Stockage bloqué (cookies et données de site bloqués par l'utilisateur, page dans un cadre tiers) | Le simple accès à localStorage peut lever une SecurityError |
| Navigation privée | Le stockage fonctionne, mais tout est effacé à la fermeture de la fenêtre |
La réponse est la même dans tous les cas : entourer les accès d'un try…catch et prévoir une valeur par défaut. Voici le module stockage.js de Gestock :
const CLE = 'gestock.pieces';
export function chargerPieces(parDefaut) {
try {
const json = localStorage.getItem(CLE);
if (json === null) return parDefaut;
const donnees = JSON.parse(json);
return Array.isArray(donnees) ? donnees : parDefaut;
} catch (erreur) {
console.warn('Stock local illisible, données par défaut utilisées.', erreur);
return parDefaut;
}
}
export function sauvegarderPieces(pieces) {
try {
localStorage.setItem(CLE, JSON.stringify(pieces));
return true;
} catch (erreur) {
console.warn("Impossible d'enregistrer le stock.", erreur);
return false;
}
}
chargerPieces vérifie aussi que ce qu'elle a lu est bien un tableau (Array.isArray) : du JSON valide n'est pas forcément du JSON attendu. sauvegarderPieces renvoie true ou false pour que l'interface puisse prévenir l'utilisateur si l'enregistrement a échoué.
Ce qu'il ne faut jamais stocker : les jetons et les données sensibles
Tout script qui s'exécute dans ta page peut lire tout le localStorage et le sessionStorage de ton site. Ton code, mais aussi celui des bibliothèques que tu charges, d'un outil de statistiques, d'une extension malveillante, ou d'un attaquant qui a réussi un XSS (chapitre 7). Il n'existe aucun moyen de réserver une clé à un seul script.
D'où la règle : n'y range rien qui donnerait un pouvoir à celui qui le lit.
| À ne jamais stocker côté navigateur | Pourquoi |
|---|---|
| Un jeton d'authentification (JWT, jeton d'accès, jeton de rafraîchissement) | Volé par un XSS, il permet d'agir au nom de l'utilisateur depuis n'importe où, jusqu'à son expiration |
| Un mot de passe, même haché | Aucune raison qu'il quitte le formulaire de connexion |
| Une clé d'API secrète | Le code et le stockage du navigateur sont publics par nature |
| Des données personnelles sensibles (santé, coordonnées bancaires) | Lisibles par tout script, conservées sur un poste parfois partagé |
Où mettre la session d'un utilisateur connecté, alors ? La recommandation actuelle pour une application web : un cookie de session posé par le serveur avec les attributs HttpOnly (illisible par JavaScript), Secure (envoyé seulement en HTTPS) et SameSite=Lax ou Strict (pas envoyé par des requêtes venues d'autres sites). Ton JavaScript ne voit jamais le jeton ; le navigateur l'envoie tout seul à l'API. Un XSS reste grave (le script peut agir pendant que la page est ouverte), mais il ne peut pas voler la session pour l'utiliser ailleurs.
Enfin, le droit s'applique aussi à ce stockage. La CNIL considère que les règles des cookies (article 82 de la loi Informatique et Libertés) visent tous les « traceurs », y compris ce qu'une page écrit dans localStorage : un stockage strictement nécessaire au service que l'utilisateur demande (son panier, ses préférences d'affichage, le stock de Gestock) est dispensé de consentement, un stockage de mesure d'audience ou de publicité ne l'est pas (cours Droit et RGPD).
Synchroniser plusieurs onglets : l'événement storage
Quand une page modifie localStorage, les autres onglets ouverts sur le même site reçoivent un événement storage (pas l'onglet qui a fait la modification). Gestock peut s'en servir pour rester à jour si le gérant l'a ouvert deux fois :
window.addEventListener('storage', (event) => {
if (event.key === 'gestock.pieces') {
etat.pieces = chargerPieces([]);
rafraichir();
}
});
Voir et modifier le stockage dans les outils de développement
Dans Chrome et Edge, l'onglet Application, rubrique Stockage local (Local storage), affiche les clés et les valeurs de chaque origine. Firefox les montre dans l'onglet Stockage, Safari dans Stockage. Tu peux y modifier une valeur à la main (pratique pour simuler des données corrompues et tester ton try…catch), supprimer une clé, ou tout effacer pour retrouver une première visite. Le bouton « Effacer les données du site » de l'onglet Application vide d'un coup stockage, cookies et cache.
Fil rouge : Gestock garde son stock
Dans main.js, deux changements suffisent : enregistrer après chaque modification, relire au démarrage.
import { chargerPieces, sauvegarderPieces } from './stockage.js';
function modifierStock(pieces) {
etat.pieces = pieces;
sauvegarderPieces(etat.pieces); // nouveau : chaque changement est enregistré
rafraichir();
}
const PIECES_DE_DEPART = [ /* le stock de départ du chapitre 1 */ ];
etat.pieces = chargerPieces(PIECES_DE_DEPART);
rafraichir();
Recharge la page après avoir ajouté une pièce : elle est toujours là. Ouvre l'onglet Application : la clé gestock.pieces contient le tableau en JSON. Au chapitre 10, le stock de départ viendra d'une API au lieu d'être écrit dans le code.
Attention, ce stockage reste local au navigateur : le gérant ne verra pas sur son téléphone ce qu'un technicien a saisi sur le poste de l'atelier. Pour partager les données, il faut un serveur et une base, c'est-à-dire une API (créer une API REST avec Node.js et Express).
Exercices
Exercice 1 sur 4 Ce que renvoie le stockage Réussi À revoir
Après ces instructions, que valent a, b, c et d ?
localStorage.clear();
localStorage.setItem('n', 5);
localStorage.setItem('liste', ['SSD-512', 'RAM-16']);
localStorage.setItem('obj', JSON.stringify({ ok: true }));
const a = localStorage.getItem('n') + 1;
const b = localStorage.getItem('liste');
const c = JSON.parse(localStorage.getItem('obj')).ok;
const d = localStorage.getItem('absente');
Voir la réponseMasquer la réponse de l'exercice 1
avaut'51': le nombre 5 a été stocké comme la chaîne'5', et'5' + 1concatène.bvaut'SSD-512,RAM-16': un tableau converti directement en chaîne (par sa méthodetoString), sans les crochets. Relire un tableau ainsi est impossible de façon fiable ; il fallaitJSON.stringify.cvauttrue: l'objet est passé par JSON dans les deux sens.dvautnull.
Tu avais trouvé ?
Exercice 2 sur 4 Mémoriser le filtre Réussi À revoir
Gestock a une case « Seulement les pièces en alerte ». Fais en sorte que son état soit retenu quand l'utilisateur recharge la page, avec la clé gestock.alertesSeules. Le stockage peut être indisponible : l'application ne doit jamais planter pour autant.
Voir la réponseMasquer la réponse de l'exercice 2
const CLE_FILTRE = 'gestock.alertesSeules';
function lireFiltre() {
try {
return localStorage.getItem(CLE_FILTRE) === 'true';
} catch {
return false;
}
}
caseAlertes.checked = lireFiltre();
etat.alertesSeules = caseAlertes.checked;
caseAlertes.addEventListener('change', () => {
etat.alertesSeules = caseAlertes.checked;
try {
localStorage.setItem(CLE_FILTRE, String(caseAlertes.checked));
} catch {
// Stockage indisponible : le filtre marche, il ne sera simplement pas retenu.
}
rafraichir();
});
Le booléen est stocké sous forme de chaîne ('true' ou 'false') et relu par comparaison. catch sans paramètre est permis quand on n'a pas besoin de l'erreur.
Tu avais trouvé ?
Exercice 3 sur 4 Revue de code : connexion Réussi À revoir
Un étudiant a écrit ce code pour la connexion à son application d'E6. Relève les problèmes de sécurité et propose une architecture correcte.
const reponse = await fetch('/api/connexion', { method: 'POST', body: JSON.stringify({ email, motDePasse }) });
const { jeton } = await reponse.json();
localStorage.setItem('jeton', jeton);
localStorage.setItem('motDePasse', motDePasse); // pour la reconnexion automatique
Voir la réponseMasquer la réponse de l'exercice 3
- Le mot de passe est stocké en clair dans
localStorage: n'importe quel script de la page (XSS, bibliothèque compromise, extension) peut le lire, et il reste sur le poste. Il ne doit jamais être stocké côté client. - Le jeton est dans
localStorage: un XSS peut le lire et l'envoyer à l'attaquant, qui l'utilise ensuite depuis sa propre machine jusqu'à son expiration. - (Hors sécurité) Le
fetchenvoie du JSON sans en-têteContent-Type: application/jsonet sans vérifierreponse.ok(chapitre 10).
Architecture correcte : le serveur vérifie le mot de passe, puis pose un cookie de session HttpOnly, Secure, SameSite=Lax, que JavaScript ne peut pas lire. Le navigateur l'envoie automatiquement aux requêtes suivantes vers la même origine. La « reconnexion automatique » se fait avec une session de plus longue durée côté serveur, révocable, jamais en rejouant un mot de passe stocké.
Tu avais trouvé ?
Exercice 4 sur 4 sessionStorage ou localStorage ? Réussi À revoir
Pour chaque donnée, choisis localStorage, sessionStorage, un cookie posé par le serveur, ou « rien côté navigateur ».
- Le thème clair ou sombre choisi par l'utilisateur.
- L'étape 2 sur 4 d'un formulaire de commande, à retrouver après un rechargement accidentel de l'onglet.
- La session d'un technicien connecté.
- Le numéro de carte bancaire saisi pour un paiement.
- Le brouillon d'un long compte rendu d'intervention.
Voir la réponseMasquer la réponse de l'exercice 4
localStorage: une préférence à retrouver à chaque visite.sessionStorage: utile seulement dans cet onglet, effacé à sa fermeture.- Un cookie de session
HttpOnly,Secure,SameSiteposé par le serveur. - Rien côté navigateur : le paiement passe par le prestataire de paiement, la carte n'est stockée ni dans le navigateur ni dans ta base.
localStorage, pour ne rien perdre si le navigateur se ferme ; à effacer une fois le compte rendu envoyé. S'il contient des données personnelles de clients, garde-le le moins longtemps possible.
Tu avais trouvé ?