Un audit rapide chez un nouveau client nous a mis la puce à l’oreille début janvier : son front headless affichait, sans le vouloir, le lien vers l’archive des articles d’un auteur qui n’existait plus que dans la base WordPress, ainsi qu’un champ d’URL personnel jamais renseigné volontairement pour un usage public. Le point de terminaison /wp-json/wp/v2/users, consulté depuis les articles pour afficher le nom de l’auteur, en révélait bien plus que nécessaire.
Cette recette ne traite pas de l’énumération des utilisateurs par force brute, un sujet de sécurité déjà couvert ailleurs sur ce blog. Elle porte spécifiquement sur ce qu’un front headless choisit d’afficher, et sur la manière de couper court à ce qui ne devrait jamais sortir en clair.
Ce que révèle le contexte view par défaut
Sans authentification, l’API REST limite déjà une partie des champs utilisateur au contexte view plutôt qu’edit : l’adresse email brute n’apparaît pas telle quelle dans la réponse standard d’un appel anonyme, contrairement à une idée reçue. En revanche, des champs comme url, description, link (l’archive des articles de l’auteur) et un avatar_urls pointant vers Gravatar restent bien présents, avec parfois plus de détails qu’un front public ne devrait relayer.
curl -s https://exemple.fr/wp-json/wp/v2/users/3 | jq
{
"id": 3,
"name": "Julie Fontaine",
"url": "",
"description": "Ancienne stagiaire, compte jamais désactivé",
"link": "https://exemple.fr/author/julie-fontaine/",
"slug": "julie-fontaine",
"avatar_urls": { "24": "...", "48": "...", "96": "..." }
}
Restreindre l’exposition avec rest_prepare_user
Le filtre natif rest_prepare_user permet de retirer, au vol, tout champ jugé non pertinent avant que la réponse ne parte du serveur :
add_filter( 'rest_prepare_user', function ( $response, $user, $request ) {
$data = $response->get_data();
unset( $data['url'] );
unset( $data['description'] );
unset( $data['avatar_urls'] );
// On garde uniquement ce dont un front a réellement besoin :
$response->set_data( array(
'id' => $data['id'],
'name' => $data['name'],
'slug' => $data['slug'],
'link' => $data['link'],
) );
return $response;
}, 10, 3 );

Le cas des comptes désactivés ou obsolètes
Le point de terminaison /wp-json/wp/v2/users continue de lister tout utilisateur ayant publié au moins un article, même un compte d’ancien collaborateur jamais supprimé. Pour un front qui n’a besoin que des auteurs actifs, il est possible de filtrer la liste elle-même avec rest_user_query, en imposant par exemple un rôle minimal :
add_filter( 'rest_user_query', function ( $args, $request ) {
$args['role__in'] = array( 'author', 'editor', 'administrator' );
return $args;
}, 10, 2 );
Ce filtre n’empêche pas de retrouver un article déjà publié par un ancien compte, mais évite que ce compte n’apparaisse dans une liste d’auteurs proposée par le front, par exemple pour un filtre de recherche par rédacteur.
Faut-il aller jusqu’à masquer l’email ?
Par défaut, le champ email n’est déjà retourné qu’en contexte edit, réservé aux comptes authentifiés avec la capacité list_users. Un front headless anonyme ne devrait donc jamais le recevoir. La véritable vigilance porte plutôt sur les intégrations tierces : un plugin de formulaire ou un module de commentaires mal configuré peut, lui, demander le contexte edit avec des identifiants trop larges, et exposer l’email par un chemin détourné. Vérifier régulièrement les rôles et capacités des comptes utilisés par les intégrations reste la meilleure protection.
Vérifier le résultat après coup
- Comparer une réponse avant et après le filtre avec
curl, pour confirmer que seuls les champs voulus restent présents. - Contrôler que la liste
/wp-json/wp/v2/usersne renvoie plus de comptes désactivés ou sans rôle éditorial actif. - Tester également le point de terminaison
embedutilisé par les articles (_embedded.author), qui applique le même filtre mais mérite une vérification séparée.
Un compte utilisateur WordPress n’a jamais été pensé pour un affichage public complet : c’est un profil d’administration avant tout, et un front headless doit le traiter comme une ressource à filtrer, pas à recopier telle quelle.
En résumé
Le point de terminaison des utilisateurs mérite le même traitement que n’importe quelle autre ressource sensible de l’API REST : un filtre explicite, appliqué en connaissance de cause, plutôt qu’une confiance aveugle dans les réglages par défaut. Quelques lignes suffisent pour ramener la réponse à l’essentiel : un nom, un identifiant, un lien vers l’archive de l’auteur, rien de plus.