samedi 26 septembre 2026

À propos

Contact

Headless & API

JWT expiré en plein parcours d’achat headless : le piège du refresh silencieux

Un token JWT qui expire pendant qu'un visiteur remplit son panier casse le parcours sans message clair. Voici comment rafraîchir le jeton en silence, sans faire perdre la session.

Par Clément Hadrot • 6 octobre 2021 • 4 min de lecture • Aucun commentaire
JWT expiré en plein parcours d'achat headless : le piège du refresh silencieux

Symptôme

Un client d’un site de vente d’équipements sportifs nous a signalé un comportement étrange : certains visiteurs abandonnaient leur panier après plusieurs minutes passées à comparer des tailles et des coloris, sans message d’erreur visible, sans plantage apparent. Le bouton « Ajouter au panier » restait cliquable, mais plus rien ne se passait après le clic. Aucune erreur dans la console du navigateur ne remontait clairement la cause, seulement un code retour 403 discret dans l’onglet réseau, ignoré silencieusement par le code du front.

Diagnostic

Le front headless en Vue.js authentifiait chaque visiteur via un plugin JWT Authentication pour WordPress, avec un jeton d’une durée de vie de dix minutes, valeur par défaut sur ce projet. Le panier, lui, était géré côté serveur pour permettre une synchronisation entre appareils, ce qui obligeait chaque ajout de produit à passer par un appel authentifié à un point de terminaison personnalisé. Un visiteur qui prenait plus de dix minutes à choisir sa taille voyait son jeton expirer en silence : l’appel suivant recevait une réponse 403 du plugin JWT, que le front se contentait de logguer sans rien afficher ni tenter de corriger.

{
  "code": "jwt_auth_invalid_token",
  "message": "Le jeton d'authentification a expiré",
  "data": { "status": 403 }
}

Le problème n’était donc pas un bug de panier, mais une absence totale de gestion de l’expiration du jeton pendant un parcours volontairement plus long qu’une simple lecture de contenu.

L'essentiel à retenir : Un JWT expiré en plein remplissage renvoie une 403 muette ; Un intercepteur de requêtes évite l'interruption visible ; Un jeton de rafraîchissement change tout le comportement

Correctif : un rafraîchissement silencieux avant chaque appel sensible

La solution retenue n’a pas consisté à allonger la durée de vie du jeton, ce qui aurait affaibli la sécurité du compte sans résoudre le problème de fond pour les parcours plus longs. À la place, un intercepteur Axios a été ajouté pour détecter toute réponse 403 liée à un jeton expiré, tenter un rafraîchissement transparent, puis rejouer la requête initiale sans que le visiteur ne perçoive quoi que ce soit :

axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    const original = error.config
    const estJetonExpire =
      error.response?.status === 403 &&
      error.response?.data?.code === 'jwt_auth_invalid_token'

    if (estJetonExpire && !original._dejaTente) {
      original._dejaTente = true
      try {
        const nouveauJeton = await rafraichirJeton()
        localStorage.setItem('jwt', nouveauJeton)
        original.headers.Authorization = `Bearer ${nouveauJeton}`
        return axios(original)
      } catch (echecRafraichissement) {
        redirigerVersConnexion()
      }
    }
    return Promise.reject(error)
  }
)

La fonction rafraichirJeton s’appuie sur le point de terminaison /wp-json/jwt-auth/v1/token/validate pour confirmer la validité d’un jeton de rafraîchissement stocké séparément, avant de générer un nouveau jeton d’accès de courte durée. Ce jeton de rafraîchissement, lui, est configuré avec une durée de vie plus longue, un mois dans notre cas, et stocké dans un cookie HttpOnly plutôt que dans le localStorage, pour limiter les risques de vol par script.

Prévention

  • Tout appel authentifié critique (panier, commande, compte) passe désormais systématiquement par cet intercepteur, jamais par un appel direct sans filet.
  • Un test automatisé simule volontairement l’expiration du jeton en cours de parcours, pour vérifier que le rafraîchissement silencieux fonctionne avant chaque mise en production.
  • Un indicateur discret de session, affiché uniquement en développement, permet à l’équipe de repérer immédiatement un jeton sur le point d’expirer pendant les tests manuels.

Ce que ce correctif ne règle pas

Ce mécanisme suppose que le jeton de rafraîchissement lui-même reste valide. Un visiteur qui laisserait un onglet ouvert pendant plusieurs semaines sans revenir finirait par devoir se reconnecter, ce qui reste un compromis raisonnable entre sécurité et confort d’usage. Ce choix entre stocker un jeton côté client et gérer une session serveur avec cookies mérite d’ailleurs un comparatif à part entière, déjà traité dans un autre article de ce blog à propos de Remix.

Un jeton qui expire n’est jamais le vrai problème : le vrai problème, c’est un front qui ne sait pas quoi faire quand ça arrive.

En résumé

Un parcours d’achat headless qui dure plus longtemps qu’une session de lecture classique finit toujours par croiser un jeton expiré. Plutôt que d’allonger artificiellement sa durée de vie, un rafraîchissement silencieux intercepté au bon endroit règle le problème sans compromettre la sécurité, et sans jamais que le visiteur ne s’aperçoive qu’il vient d’être reconnecté sous le capot.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi