Bugs de cache côté client : arrêter l'affichage des données d'un autre utilisateur
Apprenez à prévenir les bugs de cache côté client qui affichent les données d’un autre utilisateur en auditant les clés de cache, les règles d’invalidation et les cas sur appareils partagés.

À quoi ressemble « les données du mauvais utilisateur » dans des applis réelles
Ce bug se manifeste souvent par un moment de « attendez, pourquoi je vois ça ? ». Quelqu’un ouvre l’appli et voit le nom, l’avatar, l’adresse ou la dernière commande d’une autre personne. Parfois cela ne clignote qu’une seconde avant que l’écran ne se corrige. D’autres fois ça reste jusqu’à un rafraîchissement complet ou une réinstallation.
Vous le verrez souvent lors de :
- Déconnexion puis connexion avec un autre compte
- Changement d’espace de travail / d’organisation
- Changement de rôle (admin vers membre, ou l’inverse)
- Rafraîchissement de token et flux « se souvenir de moi » sur des appareils partagés
Un simple exemple : vous testez votre appli sur un iPad partagé. Vous vous connectez en tant que Client A, consultez les commandes, puis vous déconnectez. Un collègue se connecte en tant que Client B et ouvre la page Commandes. L’appli affiche la liste du Client A parce que la réponse mise en cache a été stockée sous une clé générique comme "orders" au lieu d’une clé scoppée comme "orders:userId".
Ce n’est pas juste un glitch d’interface. C’est un problème de confidentialité. Même un bref flash peut exposer des données personnelles comme des emails, adresses, factures ou tickets de support. Dans des secteurs réglementés, ça peut devenir un problème de conformité. Même quand rien de sensible ne fuit, la confiance chute vite.
L’objectif est simple : chaque personne doit voir uniquement ses propres données, tout le temps — après rafraîchissement, lors d’un changement d’onglet, en mode hors ligne, et lors de connexions sur appareils partagés.
Où se produit le cache côté client (carte rapide)
Quand une appli affiche les données d’une mauvaise personne, la cause n’est généralement pas « le cache » en général. C’est l’un des endroits où l’état peut persister après un changement d’identité. Voici une carte rapide pour regarder aux bons endroits.
1) Le cache HTTP intégré du navigateur
Les navigateurs peuvent mettre en cache les requêtes GET selon l’URL, les en-têtes et les règles de cache. Si vos réponses API varient selon l’utilisateur mais que la réponse est cacheable, le navigateur peut rejouer la dernière réponse même après déconnexion et reconnexion.
C’est moins courant pour des API JSON authentifiées (elles sont typiquement non-cacheable), mais cela peut arriver avec des en-têtes manquants ou des proxies/CDN mal configurés.
2) Caches au niveau de l’appli que vous contrôlez
La plupart des problèmes de mauvais utilisateur viennent des caches dans l’appli :
- État en mémoire (stores globaux, singletons, variables au niveau du module)
- localStorage/sessionStorage (persiste entre onglets et rechargements)
- IndexedDB (commun dans les applis orientées offline)
- « Réponses sauvegardées » mises de côté pour la performance
Si l’un de ces éléments est indexé trop largement (ou pas indexé du tout), un compte peut voir les données d’un autre sur un appareil partagé.
3) Bibliothèques de récupération de données avec cache de requêtes
Des bibliothèques comme React Query, SWR, Apollo et RTK Query mettent en cache les résultats en utilisant une clé que vous fournissez (ou génèrent). Si cette clé n’inclut pas le contexte d’identité courant, la bibliothèque peut servir un résultat mis en cache de la session précédente.
Cela ressemble souvent à : « Changer de compte, et le widget profil affiche toujours l’ancien nom jusqu’au rafraîchissement. »
4) Service workers et caches hors ligne
Les service workers peuvent mettre en cache HTML, réponses API et assets. Si les règles de cache sont trop larges, ils peuvent mettre en cache des réponses API personnalisées et les rejouer hors ligne ou lors d’une connexion instable.
5) Mismatch SSR/CSR à l’hydratation
Si vous rendez côté serveur, le client peut hydrater en utilisant un état obsolète d’une session précédente (ou d’un store persistant). Un pattern courant est l’UI qui charge d’abord des données utilisateur mises en cache, puis se « corrige » après une requête. Cette « correction » peut quand même être une fuite de confidentialité.
Auditez vos clés de cache pour éviter les collisions d’utilisateurs
La plupart des bugs de mauvais utilisateur commencent par une clé de cache trop large. Si deux sessions différentes peuvent produire la même clé, l’appli peut retourner « correctement » la mauvaise réponse mise en cache.
Une clé sûre décrit à la fois :
- ce que sont les données, et
- pour qui elles sont (et sous quel scope).
Une bonne règle : si un changement modifie ce que le serveur est autorisé à renvoyer, la clé de cache doit aussi changer.
En pratique, les clés (ou namespaces) doivent généralement inclure :
- Identifiant utilisateur (ou autre identifiant de compte stable)
- Identifiant tenant/org/workspace (pour les applis multi-tenant)
- Rôle ou scope de permission (admin vs membre)
- Locale (si le contenu change selon langue/région)
- Forme de la requête comme filtres et pagination
Évitez des clés comme "me", "profile", "dashboard" ou "inbox" sauf si elles sont explicitement scoppées par utilisateur.
- Mauvais : "profile" ou "me"
- Bon : "user:123:org:55:profile"
Même idée pour les listes :
- Mauvais : "orders?page=1"
- Bon : "user:123:org:55:orders?status=open&page=1"
Pour repérer rapidement des collisions, cherchez des chaînes de clé partagées et voyez où elles sont réutilisées. Si possible, logguez la clé de cache calculée à l’exécution, puis changez de compte et comparez.
Règles d’invalidation de cache qui correspondent aux actions réelles des utilisateurs
La plupart des bugs de mauvais utilisateur ne sont pas compliqués. L’appli continue d’utiliser des données qui étaient correctes il y a cinq minutes, mais pour une autre personne.
Commencez par décider ce qui doit être vidé (ou resscoapé) quand l’identité change. La déconnexion est évidente, mais le changement de compte est le piège courant : l’UI met à jour le badge du « compte courant » tandis que les requêtes en cache pointent encore vers l’utilisateur précédent.
Une règle simple qui marche : quand le « contexte utilisateur courant » change (ID utilisateur, ID org, ID workspace), traitez-le comme un mini redémarrage des données scoppées par utilisateur.
Déclencheurs pratiques à brancher :
- Connexion, déconnexion, changement de compte : effacer les caches scoppés utilisateur, annuler les requêtes en cours, relancer les requêtes essentielles.
- Tout changement affectant ce que l’utilisateur peut voir : mise à jour de rôle/permission, changement d’organisation/projet.
- Événements d’auth : échec de rafraîchissement de token, ré-auth forcée, réponses « session expirée ».
Les TTL ne sont pas que des réglages de performance. Ce sont des contrôles de confidentialité. Le contenu public peut vivre plus longtemps, mais tout ce qui est lié à l’identité (profil, facturation, permissions, activité récente) doit expirer rapidement, surtout sur des appareils partagés.
Gardez les réinitialisations de cache faciles à appeler depuis un seul endroit. Créez une fonction unique (par exemple resetUserState()) qui efface les bons caches et réinitialise l’appli, et appelez-la depuis les flux de connexion/déconnexion/changement de compte. Quand la logique de reset est dispersée sur plusieurs écrans, un chemin finira par être oublié.
Étape par étape : reproduire et déboguer le problème
La façon la plus rapide de corriger ces bugs est de les provoquer à la demande. Utilisez un profil de navigateur (ou un appareil de test partagé) et deux comptes de test avec des données clairement différentes (nom, avatar et au moins un enregistrement unique).
Notez les clics exacts au fur et à mesure. Les petits détails comptent : quel onglet vous avez ouvert en premier, si vous avez utilisé back/forward, et si vous vous êtes déconnecté ou avez juste changé de compte.
Un runbook qui expose souvent la fuite :
- Connectez-vous en tant que Compte A et visitez la page qui montrera ensuite les mauvaises données.
- Sans fermer l’onglet, déconnectez-vous. Connectez-vous en tant que Compte B. Revenez sur la même page en suivant le même chemin de navigation (y compris back/forward si vous l’avez utilisé).
- Inspectez le stockage du navigateur (Local Storage, Session Storage, IndexedDB) et les panneaux de cache. Cherchez des entrées mises en cache qui n’ont pas changé lors du changement.
- Ajoutez des logs temporaires autour des lectures et écritures du cache : logguez la clé de cache, l’identifiant utilisateur courant, et d’où viennent les données (mémoire, stockage, réseau).
- Vérifiez le panneau Réseau. Si la réponse du serveur est pour le Compte B mais que l’UI affiche le Compte A, le bug est côté client.
Un indice rapide : si les deux utilisateurs frappent la même clé comme profile:me, vous pouvez lire le profil du Compte A après que le Compte B se soit connecté. Logger la clé plus l’ID utilisateur courant rend cela évident.
Cas limites sur appareils partagés et multi-comptes à tester
Les appareils partagés sont là où ça devient embarrassant rapidement. « Déconnexion » supprime souvent un token, mais pas les données mises en cache qui ont été récupérées avec ce token.
Un exemple réaliste : vous testez l’appli sur une tablette familiale. Vous vous déconnectez, quelqu’un d’autre se connecte, et l’écran d’accueil affiche brièvement votre tableau de bord avant de se mettre à jour. Même si ça se corrige, ce flash est une fuite de confidentialité.
L’utilisation multi-comptes dans le même navigateur est un autre point sensible. Deux onglets peuvent finir avec des états de session différents, surtout si un onglet rafraîchit des tokens ou réhydrate l’état pendant que l’autre affiche encore d’anciennes données. Si votre cache est global (non lié à l’identité), les collisions deviennent probables.
Le bouton Retour est un cas spécial. Certains navigateurs gardent les pages en mémoire via le back-forward cache (bfcache). Après une déconnexion, un utilisateur peut appuyer sur Retour et voir instantanément un écran précédemment connecté — y compris des données en cache — avant que votre appli ne relance les vérifs d’auth.
Exécutez ces tests avant livraison :
- Connectez-vous en tant qu’Utilisateur A, ouvrez une page lourde en données, déconnectez-vous, puis connectez-vous en tant qu’Utilisateur B sans recharger le navigateur.
- Répétez sur un appareil partagé, et aussi fermez et rouvrez l’onglet après la déconnexion.
- Ouvrez deux onglets : connectez-vous en A dans l’onglet 1, B dans l’onglet 2, puis rafraîchissez les deux et surveillez le mélange d’UI.
- Après la déconnexion, appuyez sur Retour et confirmez que vous ne voyez jamais les données de A, même brièvement.
Pièges du service worker et cache hors ligne
Les service workers sont excellents pour la vitesse, mais ils peuvent aussi rejouer les mauvaises données d’un utilisateur. Le plus grand risque est d’utiliser des stratégies cache-first ou stale-while-revalidate sur des requêtes dépendant de l’identité.
Exemple : une tablette partagée dans un magasin. L’utilisateur A se connecte et ouvre le tableau de bord, puis se déconnecte. L’utilisateur B se connecte plus tard, mais le service worker sert une réponse en cache pour "/api/me" ou "/api/orders" avant que la mise à jour réseau n’arrive. Pendant un instant (ou plus longtemps si le réseau échoue), B voit les données de A.
Cache-first et stale-while-revalidate conviennent généralement aux fichiers publics et statiques (app shell, icônes, CSS). Ils sont risqués pour les endpoints scoppés par utilisateur, surtout quand l’URL de la requête n’inclut pas un identifiant utilisateur et repose sur des cookies ou des bearer tokens.
En cas de doute, contournez le caching pour tout ce qui est lié à l’identité :
- Endpoints comme "/api/me", "/api/profile", "/api/billing", "/api/orders"
- Toute requête avec un en-tête Authorization
- Tout ce qui retourne des PII (emails, adresses, factures)
Si vous changez les règles de login, de logout, de rafraîchissement de token ou de rôle, traitez cela comme un changement cassant pour vos caches aussi. Versionnez les caches (par exemple "app-v5") et supprimez les anciens caches à l’activation. Sinon, d’anciennes réponses peuvent continuer à apparaître après que vous pensez avoir corrigé le bug.
Le mode hors ligne demande une attention supplémentaire. Gardez les données hors ligne strictement par utilisateur et effacez-les à la déconnexion. Si vous stockez des requêtes en file ou des réponses API en cache, incluez l’ID utilisateur courant dans la clé de stockage, et refusez les lectures quand l’utilisateur connecté change.
Vérifications sécurité et confidentialité (simples mais critiques)
Quand le cache côté client affiche les données d’une mauvaise personne, traitez cela d’abord comme un incident de sécurité et ensuite comme un bug d’UI.
La règle clé : ne jamais se fier au cache client pour décider qui est autorisé à voir quelque chose. L’état en cache peut être obsolète, pollué, ou copié entre sessions. L’autorisation doit être appliquée côté serveur, à chaque fois.
Un check de réalité rapide : si quelqu’un change l’ID utilisateur dans une requête (ou rejoue une ancienne requête), le serveur renvoie-t-il encore des données ? Si oui, l’UI n’est pas votre seul problème.
Contrôles minimaux à réaliser :
- Vérifier que chaque endpoint contrôle l’identité côté serveur et scope les données à cette identité.
- Confirmer que la déconnexion invalide les sessions côté serveur et les refresh tokens, pas seulement l’état UI.
- Réduire les données sensibles dans les stockages longue durée (localStorage, IndexedDB). Les garder en mémoire quand c’est possible.
- S’assurer que les réponses mises en cache ne sont pas partagées entre comptes sur le même appareil.
- Chercher dans la base de code des secrets exposés ou tokens codés en dur.
Encore un exemple : si votre UI affiche brièvement un " /me " mis en cache après la déconnexion, c’est mauvais. Si le serveur accepte aussi d’anciens tokens, c’est pire : la personne suivante pourrait charger de vraies données, pas seulement des pixels périmés.
Erreurs courantes qui causent l’affichage du mauvais utilisateur
Les bugs de mauvais utilisateur viennent généralement d’un problème sous-jacent : l’appli oublie que les données en cache doivent être liées à une identité, pas seulement à un écran.
Causes fréquentes :
- Un store global ou un singleton survit à la déconnexion, si bien que la connexion suivante hérite des données de l’utilisateur précédent.
- Des requêtes démarrées avant la déconnexion se terminent ensuite, et leurs réponses sont écrites dans le cache de la nouvelle session.
- Les clés de cache sont trop génériques ("/me", "dashboard") et n’incluent pas l’utilisateur, le tenant, le rôle ou l’environnement.
- Les mises à jour optimistes écrivent dans un cache partagé sans vérifier le scope utilisateur actif.
- La déconnexion ne nettoie que ce que vous voyez (l’état UI) mais laisse la source de vérité intacte (cache mémoire, stockage, caches de service worker).
Deux vérifications rapides qui attrapent beaucoup de cas :
- Déconnectez-vous, puis reconnectez-vous en tant qu’un autre utilisateur en limitant le réseau. Regardez ce qui se rend avant que la première nouvelle requête API ne finisse.
- Déclenchez une requête lente, déconnectez-vous en plein chargement, puis reconnectez-vous. Si la réponse lente arrive et met à jour la nouvelle session, vous avez un problème d’écriture en vol.
Checklist rapide avant publication pour le cache et l’identité
Avant de déployer un correctif, faites une passe finale axée sur l’identité. Après tout changement d’auth, l’appli ne doit pas afficher les données de l’utilisateur précédent — même brièvement.
Testez sur le même appareil et profil de navigateur (c’est là que la plupart des surprises arrivent) :
- Faites un swap A->B : connectez-vous en tant qu’Utilisateur A, ouvrez des écrans riches en données, déconnectez-vous, puis connectez-vous en tant qu’Utilisateur B et revisitez les mêmes écrans.
- Confirmez que les clés de cache sont scoppées par identité partout où vous stockez des résultats (caches en mémoire, caches de requêtes, localStorage, IndexedDB).
- Faites de la déconnexion une réinitialisation complète : effacez tokens, refresh tokens, réponses API mises en cache, stores persistés et valeurs du « dernier compte sélectionné ».
- Traitez le changement de compte comme plus important que la navigation : invalidez les requêtes scoppées utilisateur, annulez les requêtes en cours, refetch sous la nouvelle identité.
- Vérifiez le comportement du service worker : versionnez les caches au déploiement, et ne servez pas de réponses API spécifiques à un utilisateur depuis un cache partagé.
Ensuite, cherchez des éléments d’identité « collants » comme les menus de profil, éléments récents et badges de notification. Ils proviennent souvent d’un cache différent du flux principal.
Étapes suivantes : ancrer la réparation
Choisissez une règle d’acceptation et rendez-la non négociable : après la déconnexion (ou le changement de compte), l’appli ne doit montrer aucune donnée de l’utilisateur précédent, même un instant. Utilisez cette phrase pour guider vos tests manuels et automatisés.
Un ordre de travail pratique qui attrape la plupart des problèmes :
- Corrigez d’abord les clés de cache pour qu’elles soient scoppées par user/org/role.
- Faites en sorte que la déconnexion et le changement de compte effacent les caches scoppés utilisateur, réinitialisent l’état en mémoire et annulent les requêtes en cours.
- Passez en revue les règles du service worker et retirez le caching des endpoints authentifiés sauf si c’est voulu et sécurisé.
- Ajoutez un test automatisé “Utilisateur A puis Utilisateur B” pour prévenir les régressions.
Si vous traitez une base de code générée par IA (outils comme Lovable, Bolt, v0, Cursor ou Replit), ces problèmes d’identité et de cache sont particulièrement fréquents car les patterns sont copiés de façon incohérente entre les écrans. FixMyMess (fixmymess.ai) se concentre sur diagnostiquer et réparer exactement ces problèmes — collisions de clés de cache, nettoyage de logout cassé et erreurs de cache service worker — et peut commencer par un audit de code gratuit pour repérer d’où viennent les données du mauvais utilisateur.
Questions Fréquentes
Pourquoi mon application affiche les données d’un autre utilisateur après déconnexion et reconnexion ?
C’est généralement une collision de cache ou d’état : des données récupérées pour l’utilisateur A sont stockées sous une clé utilisée aussi par l’utilisateur B, ou elles ne sont pas effacées quand l’identité change. Le résultat : l’app lit correctement des données en cache, mais pour la mauvaise personne.
Que doit inclure une clé de cache “sûre” pour éviter les collisions entre utilisateurs ?
Inclure à la fois ce que sont les données et pour qui elles le sont. Par défaut sûr : scoper par identifiant utilisateur plus tenant/org/workspace et tout contexte modifiant les permissions comme le rôle, puis ajouter les filtres de requête et la pagination pour éviter les collisions de listes.
Que doit-il se passer exactement lors de la déconnexion ou du changement de compte ?
Traitez cela comme un mini redémarrage des données liées à l’utilisateur. Effacez ou rescopez les caches utilisateur, annulez les requêtes en cours, réinitialisez les stores persistés et relancez les requêtes essentielles sous la nouvelle identité pour qu’aucun ancien contenu ne puisse s’afficher en premier.
Comment résoudre cela si j’utilise React Query, SWR, Apollo ou RTK Query ?
Assurez-vous que la clé de requête change quand le contexte utilisateur change, et videz ou invalidez explicitement les requêtes à la déconnexion / au changement. Envisagez aussi de désactiver les comportements du type “keep previous data” pour les requêtes liées à l’identité afin d’éviter des flashs de données périmées.
Un service worker peut-il causer l’affichage des données du mauvais utilisateur, et comment l’empêcher ?
Par défaut, ne mettez pas en cache les réponses API spécifiques à un utilisateur dans le service worker. Mettez en cache l’app shell et les assets statiques, mais laissez les requêtes authentifiées aller au réseau, et supprimez les anciens caches à l’activation pour empêcher d’anciennes réponses de réapparaître après un déploiement.
Comment le rendu SSR/CSR peut-il provoquer un bref “flash” du mauvais profil ?
Oui. Si vous hydratez l’UI depuis un état persisté côté client ou réutilisez l’état rendu côté serveur d’une session précédente, le client peut afficher des informations utilisateur obsolètes avant que la première requête n’ait fini. Corrigez cela en scoping le stockage persistant par utilisateur et en retardant l’affichage des champs sensibles jusqu’à confirmation de l’identité.
Quelle est la manière la plus rapide de reproduire et déboguer une fuite de données entre utilisateurs ?
Reproduisez-le avec un seul profil de navigateur et deux comptes de test, puis enregistrez les lectures/écritures du cache avec la clé calculée et l’identifiant utilisateur courant. Si la réponse réseau est correcte pour l’utilisateur B mais que l’UI affiche A, vous servez un état client obsolète ou vous lisez la mauvaise clé.
Quelles conditions particulières sur appareil partagé et multi-onglets faut-il tester avant la mise en production ?
Utilisez le même appareil ou profil de navigateur, changez de compte sans fermer les onglets, testez la navigation arrière/avant, et bridez le réseau pour rendre visibles les conditions de concurrence. Testez aussi deux onglets avec des comptes différents, car les caches globaux et le rafraîchissement de token peuvent mélanger les contextes.
Est-ce juste un bug d’UI, ou un vrai problème de sécurité ?
Toujours faire respecter l’autorisation côté serveur pour chaque requête, même si l’UI “cache” normalement les données. Traitez le bug client comme un incident de confidentialité, vérifiez l’invalidation des tokens/sessions à la déconnexion, et réduisez ce que vous stockez à long terme dans localStorage ou IndexedDB.
En quoi FixMyMess peut-il aider si cela vient d’une base de code générée par IA ?
Les bases générées par IA ont souvent des patterns d’état incohérents et des clés de cache différentes d’un écran à l’autre, ce qui rend ces fuites difficiles à traquer. FixMyMess (fixmymess.ai) peut commencer par un audit gratuit du code pour localiser la collision ou l’écriture obsolète, puis réparer le nettoyage à la déconnexion, le scoping des caches et les règles du service worker pour stopper définitivement le flash de mauvais utilisateur.