Une extension de prise de rendez-vous en ligne affiche un formulaire AJAX sur la page d’accueil d’un cabinet médical. Le site utilise un cache de page complet fourni par l’hébergeur, qui sert la même page HTML statique à tous les visiteurs pendant plusieurs heures. Quelques jours après la mise en production, des patients signalent une erreur « La vérification de sécurité a échoué, veuillez recharger la page » dès leur première tentative de prise de rendez-vous, sans avoir laissé la page ouverte plus de quelques minutes.
Le développeur qui reçoit ce ticket est perplexe : la durée de vie par défaut d’un nonce WordPress est de 24 heures, répartie en deux fenêtres de douze heures. Une erreur de nonce dès la première visite ne colle pas avec ce délai. La piste à suivre n’est pourtant pas la durée du nonce elle-même, mais le moment où il a été généré.
Le symptôme
L’erreur survient de façon irrégulière, parfois immédiatement, parfois après plusieurs heures, sans corrélation avec le temps réellement passé par le visiteur sur la page. Elle touche tous les visiteurs à peu près en même temps, à partir d’un certain moment de la journée, ce qui est un signal fort d’un problème lié à un contenu partagé plutôt qu’à un problème propre à chaque session.
Le diagnostic

Un nonce WordPress, généré via wp_create_nonce(), dépend de trois éléments : une action donnée, l’identifiant de l’utilisateur courant (ou 0 pour un visiteur anonyme), et une fenêtre de temps calculée côté serveur à partir de la constante NONCE_LIFE. Sur un site sans cache, ce nonce est recalculé à chaque chargement de page, donc toujours cohérent avec l’instant présent.
Sur un site avec un cache de page complet, la page HTML générée une seule fois est ensuite resservie telle quelle à tous les visiteurs suivants, nonce inclus, puisque celui-ci est imprimé en dur dans un champ caché du formulaire au moment de la génération initiale de la page. Tant que la page reste en cache, tous les visiteurs reçoivent le même nonce, celui calculé au moment précis où le cache a été rempli, pas au moment où ils chargent réellement la page dans leur navigateur.
Si le cache a été rempli à 8 h et que la durée de vie du cache dépasse la fenêtre de validité du nonce, tout visiteur arrivant après l’expiration de cette fenêtre reçoit un nonce déjà périmé, même s’il vient d’ouvrir la page pour la première fois.
Le correctif
La solution consiste à sortir la génération du nonce du flux HTML mis en cache, pour la faire arriver après coup, une fois la page déjà affichée dans le navigateur. Deux approches courantes :
1. Générer le nonce via un appel AJAX préalable
document.addEventListener( 'DOMContentLoaded', function() {
fetch( monPluginData.ajaxUrl + '?action=obtenir_nonce_rdv' )
.then( function( reponse ) { return reponse.json(); } )
.then( function( data ) {
document.getElementById( 'nonce_rdv' ).value = data.nonce;
} );
} );
add_action( 'wp_ajax_obtenir_nonce_rdv', 'renvoyer_nonce_rdv' );
add_action( 'wp_ajax_nopriv_obtenir_nonce_rdv', 'renvoyer_nonce_rdv' );
function renvoyer_nonce_rdv() {
wp_send_json( array( 'nonce' => wp_create_nonce( 'prise_rdv' ) ) );
}
Cette route AJAX n’est jamais mise en cache par la configuration habituelle d’un cache de page complet, puisqu’elle passe par admin-ajax.php, généralement exclu des règles de cache par défaut sur la plupart des configurations d’hébergement.
2. Exclure le fragment du cache via une balise ESI ou équivalent
Certains hébergeurs et certains plugins de cache proposent un mécanisme de fragment dynamique, souvent basé sur des inclusions côté serveur (ESI) ou un système de blocs non mis en cache. Cette option évite l’aller-retour AJAX supplémentaire, mais elle dépend entièrement des capacités de la solution de cache utilisée, ce qui la rend moins portable d’un hébergeur à l’autre.
Vérifier le côté serveur du traitement AJAX
Une fois le nonce généré au bon moment, le traitement du formulaire doit toujours le vérifier avec check_ajax_referer(), jamais en silence :
function traiter_prise_rdv() {
check_ajax_referer( 'prise_rdv', 'nonce' );
// Suite du traitement...
}
Un nonce figé dans une page mise en cache n’est plus un nonce, c’est un secret de moins en moins secret partagé par tous les visiteurs.
Prévention
- Documenter, dans le fichier readme de toute extension qui utilise l’AJAX front, la nécessité de générer les nonces hors du HTML mis en cache.
- Ajouter un test manuel systématique avec le cache de page activé avant chaque mise en production d’un nouveau formulaire front.
- Éviter d’imprimer un nonce directement dans le HTML dès qu’un cache de page complet est susceptible d’être activé un jour sur le site cible, même si ce n’est pas le cas au moment du développement initial.
En résumé
Ce bug n’est jamais un problème de durée de vie du nonce en tant que telle, mais un problème de moment de génération face à un cache qui fige le HTML. Dès qu’une extension distribuée doit fonctionner correctement derrière un cache de page complet, la génération du nonce doit être déplacée vers un point d’entrée non mis en cache, sans quoi l’erreur de sécurité réapparaîtra tôt ou tard, de façon difficile à reproduire pour qui ne pense pas immédiatement au cache.