localStorage, sessionStorage ou cookies : où stocker les données d'une application web ?
Une préférence conserve le mauvais réglage. Un filtre de recherche disparaît après une actualisation. Un compte reste connecté plus longtemps que prévu. Ces problèmes commencent souvent par un choix de développement ordinaire : où le navigateur doit-il conserver une donnée ?
Les applications web utilisent couramment localStorage, sessionStorage et les cookies. Tous conservent un état lié au navigateur, mais leurs durées de vie, leurs règles d’accès et leurs conséquences pour la sécurité diffèrent. Bien choisir améliore la fiabilité, facilite le diagnostic et réduit l’exposition accidentelle d’informations sensibles.
Faut-il vraiment stocker cette valeur ?
Avant de choisir une API, demandez-vous si la valeur doit survivre à une actualisation ou à une nouvelle visite. L’état ouvert ou fermé d’un menu peut rester en mémoire JavaScript. Un filtre de recherche à partager avec quelqu’un peut avoir sa place dans l’URL plutôt que dans le stockage du navigateur. Une préférence d’affichage compact peut gagner à être enregistrée localement.
Certaines données appartiennent au serveur. Les droits d’un compte, l’état d’un abonnement payant et le total faisant foi d’un panier ne doivent jamais dépendre d’une valeur modifiable dans le navigateur. Le serveur peut conserver une session authentifiée tandis que le navigateur ne détient que son identifiant.
Une règle utile : stocker le minimum nécessaire à l’expérience, sans le conserver au-delà de son utilité.
Les trois mécanismes en un tableau
| Mécanisme | Durée de vie habituelle | Envoi automatique avec les requêtes HTTP ? | Lecture par le JavaScript de la page ? | Usage courant |
|---|---|---|---|---|
localStorage | Pas d’expiration intégrée ; peut être effacé ou évincé | Non | Oui, pour l’origine concernée | Préférences non sensibles |
sessionStorage | Limité à une session de page, généralement un onglet | Non | Oui, pour l’origine et le contexte de l’onglet concernés | État temporaire propre à un onglet |
| Cookies | Selon l’expiration et le comportement du navigateur | Oui, si le domaine, le chemin et les autres règles correspondent | Souvent, sauf avec HttpOnly | Sessions gérées par le serveur et certaines préférences |
Ce sont des tendances générales, pas des garanties de sécurité. La navigation privée, les réglages de l’utilisateur, les politiques de stockage et les quotas peuvent réduire la durée de conservation. Les cookies peuvent être de session ou persistants ; sessionStorage n’est pas synonyme de session sécurisée. Web Storage n’est pas envoyé automatiquement, mais un script qui le lit peut en transmettre le contenu. Enregistrer des données localement ne garantit pas qu’elles restent sur l’appareil.
localStorage : pratique pour les préférences
localStorage propose une API synchrone de clés et de valeurs. Une origine associe le protocole, le nom d’hôte et le port ; les chemins d’une même origine partagent ce stockage. Une page peut enregistrer une chaîne puis la relire, généralement au fil des visites sur cette origine.
const KEY = "catalog:preferences:v1";
try {
localStorage.setItem(KEY, JSON.stringify({
compactCards: true,
preferredSort: "recent"
}));
const raw = localStorage.getItem(KEY);
const preferences = raw ? JSON.parse(raw) : {};
console.log(preferences.preferredSort);
} catch {
// Storage can be blocked, full, or contain invalid JSON.
console.warn("Could not save or read preferences");
}
Cet exemple enregistre et relit une préférence à la suite. Dans une application, enregistrez-la lorsque l’utilisateur la modifie et lisez-la au démarrage de l’interface. Gérez les échecs du stockage et le JSON invalide dans les deux cas, gardez des valeurs par défaut utilisables et validez la structure ainsi que les valeurs autorisées après l’analyse. Une clé comportant une version facilite les migrations : une future version de l’application pourra traiter explicitement l’ancienne structure.
La principale limite de sécurité est simple : les scripts exécutés avec l’origine de votre page peuvent lire localStorage. Si un attaquant parvient à injecter du JavaScript grâce à une faille XSS, ce code peut accéder aux données stockées. N’utilisez pas Web Storage pour conserver des mots de passe ou des identifiants de session. Les recommandations de sécurité HTML5 d’OWASP (en anglais) expliquent pourquoi l’accès par JavaScript rend ce stockage inadapté aux identifiants de session.
Si votre application affiche du contenu provenant d’utilisateurs ou d’API, consultez l’article sur l’échappement HTML et l’assainissement avant d’insérer du balisage dans une page. Un traitement correct des sorties réduit le risque XSS ; il ne transforme pas localStorage en coffre-fort pour les identifiants.
sessionStorage : utile pour un onglet, mais accessible aux scripts
sessionStorage utilise une interface similaire, mais ses données sont limitées à une origine dans un onglet de premier niveau. Elles survivent aux actualisations ordinaires. Ce mécanisme convient à une interface en plusieurs étapes, à un tri temporaire ou à un brouillon qui ne doit normalement pas persister lors de visites ultérieures. La référence française est abrégée ; la version anglaise détaille aussi la copie initiale depuis une page ouvrante.
let step = 1;
try {
const saved = Number(sessionStorage.getItem("wizard:step"));
if (Number.isInteger(saved) && saved >= 1 && saved <= 5) {
step = saved;
}
sessionStorage.setItem("wizard:step", String(step));
} catch {
// Keep the in-memory step when storage is unavailable.
}
Cet exemple à cinq étapes n’accepte qu’une étape enregistrée valide et continue en mémoire si le stockage est bloqué ou plein. Mettez la valeur enregistrée à jour lorsque l’utilisateur avance.
sessionStorage n’est pas protégé contre les failles XSS : le code de la page peut le lire. Une page possédant un opener peut recevoir une copie initiale du stockage de session de l’onglet ouvrant ; leurs modifications ultérieures restent indépendantes. La restauration du navigateur et la duplication d’onglets rendent aussi sa durée de vie moins intuitive que “jusqu’à la fermeture de l’onglet”. Ne promettez pas qu’une fermeture détruit irréversiblement un brouillon sensible.
Le stockage par onglet apporte une commodité, pas une frontière de contrôle d’accès. Si un assistant de saisie contient des informations sensibles, déterminez si le serveur doit plutôt les conserver de manière sécurisée, avec une politique explicite d’expiration et de suppression.
Les cookies pour les sessions gérées par le serveur
Les cookies se distinguent par leur envoi automatique avec les requêtes HTTP auxquelles ils s’appliquent. Le serveur peut ainsi reconnaître un identifiant de session et vérifier les droits à chaque requête.
Un serveur peut émettre cet en-tête Set-Cookie via HTTPS. MDN propose une référence française abrégée et une version anglaise plus détaillée :
Set-Cookie: __Host-session=opaque-session-id; Path=/; Secure; HttpOnly; SameSite=Lax
Ce cookie est illustratif, pas un système de session à copier sans autre conception. Le serveur doit créer un identifiant imprévisible, conserver ou vérifier sa signification côté serveur, le renouveler aux moments appropriés et l’expirer ou le révoquer à la déconnexion. Un cookie __Host- doit être défini via HTTPS avec Secure, avoir Path=/ et ne pas comporter d’attribut Domain.
Sans Max-Age ni Expires, il s’agit d’un cookie de session. La restauration de session du navigateur peut le conserver : imposez donc une durée maximale d’inactivité et une durée absolue côté serveur. À la déconnexion, invalidez la session serveur et effacez le cookie correspondant.
Les attributs répondent à des problèmes différents :
Securelimite l’envoi aux requêtes HTTPS, avec un traitement particulier pour localhost ; utilisez HTTPS en production.HttpOnlyempêche la lecture directe pardocument.cookie, ce qui protège la valeur de session contre cet accès JavaScript.SameSite=Laxlimite de nombreux envois entre sites, mais autorise encore les navigations de premier niveau entre sites avec des méthodes sûres comme GET. Ce n’est pas une protection complète contre les attaques CSRF ; n’utilisez pas GET pour modifier un état.PathetDomaindéterminent où le cookie est envoyé. Ils ne constituent pas à eux seuls une frontière d’autorisation fiable.
Un cookie HttpOnly ne résout pas une faille XSS. Un script injecté peut encore provoquer des requêtes authentifiées vers la même origine ou modifier la page. Protégez les actions qui modifient un état contre les attaques CSRF, en complément de la validation des entrées, de l’encodage des sorties et d’une politique CSP adaptée à l’application. Le guide de gestion des sessions d’OWASP (en anglais) aborde l’expiration, les attributs des cookies et la déconnexion.
Quelles données ne doivent jamais faire autorité ?
Ces trois mécanismes restent sous le contrôle du client. Les utilisateurs peuvent modifier le stockage local, leurs propres cookies ou envoyer des requêtes construites à la main. C’est utile pour les tests, mais le stockage ne peut pas décider des autorisations.
Une application enregistre par exemple role=admin dans un cookie ou dans localStorage. Afficher un menu d’administration à partir de cette valeur peut modifier l’interface, mais le serveur doit vérifier indépendamment les droits de l’utilisateur authentifié avant d’autoriser une action privilégiée. Masquer un menu ne sécurise pas l’action.
De même, conserver cartTotal peut aider à afficher un aperçu, mais le serveur doit recalculer les prix et les remises avant le paiement.
Quand l’URL est un meilleur choix
Si une valeur définit une vue partageable, préférez un paramètre d’URL à un stockage invisible dans le navigateur. Les recherches publiques, la pagination, les filtres publics et le tri s’y prêtent bien. Les actualisations, les favoris et le bouton Retour deviennent ainsi plus prévisibles.
La contrepartie est l’exposition. Une URL peut apparaître dans les journaux, l’historique et les messages partagés : elle ne doit donc pas contenir de secrets. L’encodeur, décodeur et éditeur de paramètres URL aide à examiner les paramètres répétés et leur encodage. Son article de présentation explique notamment la différence entre + et %2B. L’encodage donne à l’URL une structure correcte ; il ne cache pas son contenu.
Examiner le stockage et les requêtes dans les outils de développement
Pour un petit test, ouvrez un site dans les outils de développement de Chrome ou Edge et sélectionnez Application, ou utilisez le panneau Stockage équivalent dans Firefox. Examinez le stockage local, le stockage de session et les cookies de l’origine concernée. Safari permet aussi d’inspecter le stockage avec son inspecteur web, même si les libellés et menus varient.
Ouvrez ensuite Réseau et effectuez une action à la fois. Une écriture dans le stockage local n’est pas automatiquement un envoi réseau ; les cookies peuvent être joints aux requêtes sortantes auxquelles ils s’appliquent. Comparez les requêtes avant et après une connexion, une actualisation ou une déconnexion. Examinez les en-têtes de requête, les en-têtes de réponse Set-Cookie et le rejet des requêtes par le serveur après la déconnexion.
Le guide Web2Generators pour examiner les requêtes réseau dans les principaux navigateurs constitue un point de départ. Utilisez des comptes fictifs et des valeurs d’exemple. Les captures réseau, exports HAR et captures d’écran peuvent révéler des cookies ou des données personnelles ; relisez-les avant de partager un rapport de bogue.
Pour des tests HTTP hors du navigateur, le générateur de commandes cURL peut préparer une requête reproductible. La commande générée ne reproduit pas automatiquement l’ensemble des cookies ni le contexte de sécurité du navigateur. Pour comparer les deux, tenez compte des redirections, de la gestion des cookies et du mode d’envoi des informations d’authentification du navigateur.
Un exemple de choix pour une application
Imaginez un site de documentation avec un sélecteur de thème, une recherche et un espace de compte.
Enregistrez le choix de thème non sensible dans localStorage si cette persistance est utile et permise. Gardez une étape de formulaire temporaire en mémoire ou dans sessionStorage selon vos besoins de reprise. Placez la recherche publique et le numéro de page dans l’URL pour permettre l’ajout aux favoris. Laissez le serveur gérer la session authentifiée avec un cookie soigneusement configuré plutôt que de conserver des mots de passe dans le navigateur.
Revoir le stockage quand les besoins changent
Une préférence locale anodine peut devenir sensible si une nouvelle fonction commence à enregistrer des données de compte dans le même objet. Un passage des jetons d’accès aux cookies peut nécessiter d’autres protections CSRF. Un choix de consentement peut demander une révision de son emplacement ou de sa durée de conservation.
Documentez chaque valeur, son utilité, qui peut la lire, son expiration et ce qui se passe à la déconnexion. Testez dans plusieurs navigateurs et avec un stockage restreint. Ce bref examen évite souvent davantage de problèmes que l’ajout d’une bibliothèque.
Vérifiez ce que votre application conserve et envoie
Ouvrez le guide d’inspection des requêtes réseau, puis testez un changement de préférence, une actualisation et une déconnexion avec un compte fictif. Comparez les panneaux Stockage et Réseau après chaque action. Vérifiez que la préférence persiste seulement comme prévu et qu’une requête protégée utilisant l’ancienne session est rejetée après la déconnexion. Notez les écarts avant d’ajouter d’autres données à conserver.