Un client textile voulait valider chaque fiche produit avant publication, mais refusait catégoriquement qu’on lui crée un compte WordPress : « je ne veux pas apprendre un back-office pour trois clics par semaine ». Le lien de prévisualisation natif de WordPress, généré par get_preview_post_link(), ne fonctionne pourtant que pour un utilisateur connecté disposant des droits sur le contenu : impossible de le partager tel quel à quelqu’un d’extérieur.
La solution consiste à générer un jeton unique, stocké en meta avec une date d’expiration, et à vérifier ce jeton via une règle de réécriture ou un paramètre d’URL contrôlé, sans jamais exposer le brouillon publiquement pour autant.
Générer un jeton de partage
Le jeton est créé à la demande, généralement depuis une métabox ou un bouton dans l’écran d’édition, et enregistré avec sa date d’expiration :
function generer_lien_previsualisation( $post_id ) {
$jeton = wp_generate_password( 32, false );
$expiration = time() + ( 72 * HOUR_IN_SECONDS );
update_post_meta( $post_id, '_jeton_previsualisation', $jeton );
update_post_meta( $post_id, '_jeton_previsualisation_expire', $expiration );
return add_query_arg(
[
'apercu_client' => $jeton,
],
home_url( '?p=' . $post_id )
);
}
Vérifier le jeton avant d’afficher le brouillon
Le hook template_redirect intercepte la requête avant tout affichage, vérifie le jeton et, si tout concorde, autorise temporairement l’affichage du brouillon pour cette seule requête :

add_action( 'template_redirect', function () {
if ( empty( $_GET['apercu_client'] ) || ! is_singular() ) {
return;
}
$post_id = get_queried_object_id();
$jeton_attendu = get_post_meta( $post_id, '_jeton_previsualisation', true );
$expiration = (int) get_post_meta( $post_id, '_jeton_previsualisation_expire', true );
if ( ! hash_equals( (string) $jeton_attendu, (string) $_GET['apercu_client'] ) ) {
wp_die( __( 'Lien de prévisualisation invalide.', 'mon-theme' ), 403 );
}
if ( time() > $expiration ) {
wp_die( __( 'Ce lien de prévisualisation a expiré.', 'mon-theme' ), 410 );
}
add_filter( 'posts_results', function ( $posts ) use ( $post_id ) {
foreach ( $posts as $post ) {
if ( (int) $post->ID === $post_id && 'draft' === $post->post_status ) {
$post->post_status = 'publish'; // Autorisation temporaire, en mémoire seulement
}
}
return $posts;
} );
} );
Sécuriser le jeton
L’usage de hash_equals() plutôt qu’une simple comparaison === évite les attaques par mesure de temps sur la comparaison de chaînes. Le jeton doit aussi être suffisamment long : trente-deux caractères aléatoires générés par wp_generate_password() avec le second paramètre à false pour exclure les caractères spéciaux qui poseraient problème dans une URL.
- Ne jamais indexer ces URL : ajouter un en-tête
X-Robots-Tag: noindexlors de l’affichage via ce jeton. - Invalider le jeton après une seule consultation si le contenu est particulièrement sensible, ou le laisser réutilisable pendant sa fenêtre de validité pour un usage plus souple.
- Supprimer les deux metas une fois le contenu publié, pour ne pas laisser traîner un jeton devenu inutile.
add_action( 'transition_post_status', function ( $nouveau_statut, $ancien_statut, $post ) {
if ( 'publish' === $nouveau_statut ) {
delete_post_meta( $post->ID, '_jeton_previsualisation' );
delete_post_meta( $post->ID, '_jeton_previsualisation_expire' );
}
}, 10, 3 );
Un lien de prévisualisation signé n’est pas une porte dérobée si son jeton est long, comparé avec hash_equals et limité dans le temps : c’est un compromis raisonnable entre confort client et sécurité du contenu.
Limites de cette approche
Ce mécanisme reste adapté à un partage ponctuel avec un nombre limité de destinataires de confiance. Pour un usage massif, avec des dizaines de contenus partagés en parallèle, une table dédiée plutôt que des metas répétées sur chaque article facilite la purge automatique des jetons expirés via une tâche planifiée.
En résumé
Un jeton signé, une vérification stricte à l’affichage et une expiration claire suffisent à donner un accès temporaire et sécurisé à un brouillon, sans jamais avoir à créer de compte WordPress pour un client qui n’en a pas l’usage au quotidien.