# wp_ajax_nopriv_ correctement sécurisé : ce que doit vérifier un point d’entrée public

> Un point d'entrée AJAX ouvert aux visiteurs non connectés reste une porte d'entrée sensible. Voici les vérifications à ne jamais sauter dans une extension.

- Auteur : WordPress Développement
- Publié le : 2020-02-16
- Mis à jour le : 2020-02-16
- Catégorie : Extensions
- URL : https://www.wpmoderne.fr/extensions/securiser-ajax-nopriv-point-entree-public/

## L’essentiel

- Un nonce ne remplace pas un contrôle de capacité
- Toujours valider ET nettoyer les données reçues
- Répondre avec wp_send_json pour un format cohérent

Un point d'entrée accessible via `wp_ajax_nopriv_` répond à n'importe quel visiteur, connecté ou non, ce qui en fait par nature la surface la plus exposée d'une extension. Contrairement à un endpoint réservé aux membres, il ne peut s'appuyer sur aucune vérification de session pour filtrer les requêtes légitimes des tentatives malveillantes.

Beaucoup de développeurs découvrent ce hook en pensant qu'un simple nonce suffit à sécuriser l'action. C'est une erreur : le nonce protège contre les requêtes forgées depuis un autre site (CSRF), pas contre un visiteur qui envoie volontairement des données malformées ou hostiles directement vers l'URL d'admin-ajax.php.

## Déclarer les deux hooks selon le public visé

Une action AJAX destinée aux visiteurs non connectés s'enregistre avec `wp_ajax_nopriv_{action}`. Si la même logique doit aussi répondre aux utilisateurs connectés, il faut déclarer en plus `wp_ajax_{action}` : les deux hooks sont indépendants, WordPress ne les fait pas cohabiter automatiquement.

```
add_action( 'wp_ajax_wpm_newsletter_inscription', 'wpm_traiter_inscription' );
add_action( 'wp_ajax_nopriv_wpm_newsletter_inscription', 'wpm_traiter_inscription' );

function wpm_traiter_inscription() {
    check_ajax_referer( 'wpm_newsletter_nonce', 'nonce' );

    $email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : '';

    if ( empty( $email ) || ! is_email( $email ) ) {
        wp_send_json_error( array( 'message' => 'Adresse invalide.' ), 400 );
    }

    // Traitement métier ici.
    wp_send_json_success( array( 'message' => 'Inscription enregistrée.' ) );
}
```

## Le nonce vérifie l'origine, pas le contenu

`check_ajax_referer()` confirme que la requête provient bien d'une page où le nonce a été correctement généré et affiché, généralement via `wp_localize_script()` ou `wp_add_inline_script()`. Cela empêche un autre site d'envoyer discrètement des requêtes au nom d'un visiteur qui a ouvert une page infectée dans un autre onglet.

> L'essentiel à retenir : Un nonce ne remplace pas un contrôle de capacité ; Toujours valider ET nettoyer les données reçues ; Répondre avec wp_send_json pour un format cohérent

Mais un nonce valide n'apporte aucune garantie sur la nature des données envoyées. Rien n'empêche un visiteur d'inspecter la requête réseau, de copier le nonce affiché sur la page et de renvoyer manuellement des centaines de requêtes avec des valeurs différentes. C'est pourquoi la validation des données reste une étape distincte et obligatoire, quel que soit le résultat du nonce.

### Sanitiser à l'entrée, échapper à la sortie

- Utiliser `sanitize_email()`, `sanitize_text_field()` ou `absint()` selon le type de donnée attendu
- Ne jamais faire confiance à un champ caché du formulaire pour un identifiant de contenu sensible : le revérifier côté serveur
- Encapsuler systématiquement l'entrée brute avec `wp_unslash()` avant sanitisation, WordPress ajoutant des antislashs à `$_POST`

## Limiter ce qu'un point d'entrée public peut réellement faire

Un point d'entrée ouvert aux visiteurs non connectés ne doit jamais permettre une action qui modifie un contenu existant, écrit un fichier arbitraire ou déclenche l'envoi massif d'e-mails sans limite de fréquence. Si la fonctionnalité l'exige malgré tout, une limitation de débit devient indispensable, ne serait-ce qu'un compteur simple stocké en transient par adresse IP.

```
$cle = 'wpm_limite_' . md5( $_SERVER['REMOTE_ADDR'] );
$compteur = (int) get_transient( $cle );

if ( $compteur >= 5 ) {
    wp_send_json_error( array( 'message' => 'Trop de tentatives, réessayez plus tard.' ), 429 );
}

set_transient( $cle, $compteur + 1, MINUTE_IN_SECONDS * 10 );
```

## Répondre proprement, sans fuite d'information

Les fonctions `wp_send_json_success()` et `wp_send_json_error()` encapsulent la réponse dans un format JSON cohérent et appellent `wp_die()` en interne, ce qui évite d'oublier l'arrêt du script après l'envoi. Un message d'erreur générique côté visiteur évite en outre de révéler des détails d'implémentation utiles à un attaquant, comme la structure exacte d'une requête SQL sous-jacente.

| Vérification | Rôle | Fonction WordPress |
| --- | --- | --- |
| Origine de la requête | Anti-CSRF | check_ajax_referer() |
| Format des données | Anti-injection | sanitize_email(), absint() |
| Fréquence des appels | Anti-abus | get_transient(), set_transient() |
| Réponse | Format cohérent | wp_send_json_success(), wp_send_json_error() |

> Sur un point d'entrée public, la question à se poser n'est jamais « est-ce que ce visiteur va bien se comporter » mais « que se passe-t-il si quelqu'un envoie exactement le contraire de ce que j'attends ».

## Pour aller plus loin

Un point d'entrée AJAX public bien conçu combine toujours trois couches : la vérification d'origine par le nonce, la validation stricte du contenu, et une limite raisonnable sur la fréquence des appels. Aucune des trois ne remplace les deux autres, et c'est précisément leur combinaison qui transforme une action ouverte à tous en une fonctionnalité robuste plutôt qu'en une porte laissée entrouverte.
