Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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, sessionStorage et 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

localStoragesessionStorageCookie
Durée de vieJusqu'à suppression (par le code ou l'utilisateur)Jusqu'à la fermeture de l'ongletDate d'expiration choisie par le serveur ou le script
Partagé entreTous les onglets du même siteUn seul ongletTous les onglets ; envoyé au serveur
Envoyé au serveurNonNonOui, à chaque requête vers son domaine
TailleEnviron 5 Mo par siteEnviron 5 Mo par siteEnviron 4 Ko par cookie
Lisible par JavaScriptOui, toujoursOui, toujoursOui, sauf s'il est marqué HttpOnly
Type de valeurChaînes uniquementChaînes uniquementChaî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

js
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 :

js
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.

js
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 :

SituationCe qui se passe
Première visitegetItem renvoie null ; JSON.parse(null) renvoie null
Données modifiées à la main ou par une ancienne version de l'applicationJSON.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éeLe 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 :

js
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é navigateurPourquoi
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èteLe 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 :

js
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.

js
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 ?

js
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
  • a vaut '51' : le nombre 5 a été stocké comme la chaîne '5', et '5' + 1 concatène.
  • b vaut 'SSD-512,RAM-16' : un tableau converti directement en chaîne (par sa méthode toString), sans les crochets. Relire un tableau ainsi est impossible de façon fiable ; il fallait JSON.stringify.
  • c vaut true : l'objet est passé par JSON dans les deux sens.
  • d vaut null.

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
js
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.

js
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
  1. 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.
  2. 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.
  3. (Hors sécurité) Le fetch envoie du JSON sans en-tête Content-Type: application/json et sans vérifier reponse.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 ».

  1. Le thème clair ou sombre choisi par l'utilisateur.
  2. L'étape 2 sur 4 d'un formulaire de commande, à retrouver après un rechargement accidentel de l'onglet.
  3. La session d'un technicien connecté.
  4. Le numéro de carte bancaire saisi pour un paiement.
  5. Le brouillon d'un long compte rendu d'intervention.
Voir la réponseMasquer la réponse de l'exercice 4
  1. localStorage : une préférence à retrouver à chaque visite.
  2. sessionStorage : utile seulement dans cet onglet, effacé à sa fermeture.
  3. Un cookie de session HttpOnly, Secure, SameSite posé par le serveur.
  4. 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.
  5. 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é ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : JavaScript asynchrone : promesses, async/await et fetchRevoir le sommaire du cours