# permission_callback obligatoire : sécuriser vos routes REST personnalisées

> Depuis WordPress 5.5, omettre permission_callback déclenche un avertissement. Voici pourquoi ce paramètre est devenu incontournable et comment bien l'écrire.

- Auteur : Clément Hadrot
- Publié le : 2022-03-08
- Mis à jour le : 2022-03-08
- Catégorie : Sécurité
- URL : https://wpmoderne.dev.wordpress-developpement.fr/securite/permission-callback-api-rest-obligatoire/

## L’essentiel

- Sans permission_callback, une route reste techniquement publique par défaut
- __return_true doit être un choix assumé, jamais un oubli
- current_user_can est le bon réflexe pour les routes qui modifient des données

Il fut un temps où créer une route REST personnalisée dans WordPress se résumait à appeler `register_rest_route` avec un `callback` et rien d'autre. Ça fonctionnait, mais silencieusement : la route restait accessible à n'importe quel visiteur, authentifié ou non, sans que rien ne le signale dans le code. Beaucoup de développeurs ne s'en apercevaient qu'en production, parfois après qu'une donnée sensible avait fuité.

Depuis WordPress 5.5, sorti en août 2020, omettre le paramètre `permission_callback` déclenche un avertissement `_doing_it_wrong` visible dès que `WP_DEBUG` est actif. Ce n'est pas encore un blocage strict, mais c'est un signal clair : ce paramètre n'est plus optionnel dans l'esprit du cœur de WordPress. Voyons pourquoi, et comment l'écrire correctement selon le contexte.

## Ce que fait vraiment permission_callback

Il ne faut pas confondre authentification et autorisation. L'authentification répond à la question « qui êtes-vous ? », via un cookie de session, un mot de passe d'application ou un jeton. L'autorisation répond à la question « avez-vous le droit de faire ceci ? ». `permission_callback` gère exclusivement la seconde question, et WordPress refuse désormais de laisser cette question sans réponse explicite.

```
register_rest_route( 'monplugin/v1', '/commandes', array(
    'methods'             => 'GET',
    'callback'            => 'monplugin_get_commandes',
    'permission_callback' => function () {
        return current_user_can( 'manage_woocommerce' );
    },
) );
```

Dans cet exemple, la route liste des commandes : elle n'a évidemment pas vocation à être publique. Le callback de permission vérifie une capacité précise plutôt qu'un simple `is_user_logged_in()`, ce qui évite qu'un abonné basique puisse consulter des données réservées à la gestion de boutique.

## __return_true : un choix, pas un oubli

WordPress fournit la fonction utilitaire `__return_true`, pratique pour déclarer explicitement qu'une route est publique. Le problème n'est pas son existence, mais son usage par réflexe pour faire disparaître l'avertissement sans réfléchir à la question de fond.

> L'essentiel à retenir : Sans permission_callback, une route reste techniquement publique par défaut ; __return_true doit être un choix assumé, jamais un oubli ; current_user_can est le bon réflexe pour les routes qui modifient des données

- `__return_true` convient à une route qui affiche un contenu déjà public, comme une liste d'articles publiés
- Il ne convient jamais à une route qui lit des données utilisateur, des commandes, des messages ou des réglages
- Il ne convient jamais à une route qui écrit ou modifie des données, quelle que soit la méthode HTTP utilisée

La règle mentale la plus sûre consiste à se demander, pour chaque route : « si cette réponse JSON était affichée en clair sur la page d'accueil du site, est-ce un problème ? ». Si la réponse est oui, `__return_true` est une erreur.

## Vérifier les capacités selon le contexte

Le callback de permission peut recevoir l'objet `WP_REST_Request` en paramètre, ce qui permet des vérifications fines, par exemple limiter l'accès à un enregistrement précis plutôt qu'à toute la collection.

```
'permission_callback' => function ( WP_REST_Request $request ) {
    $commande_id = (int) $request['id'];
    $commande    = wc_get_order( $commande_id );

    if ( ! $commande ) {
        return false;
    }

    return get_current_user_id() === $commande->get_customer_id()
        || current_user_can( 'manage_woocommerce' );
},
```

Ici, un client peut consulter sa propre commande, un gestionnaire peut consulter n'importe quelle commande, et tous les autres cas renvoient `false`. C'est ce niveau de granularité que permet `permission_callback` par rapport à une simple vérification binaire connecté ou non connecté.

## Les erreurs les plus fréquentes

La première erreur consiste à vérifier une capacité dans le `callback` principal plutôt que dans `permission_callback`. Le résultat fonctionne souvent, mais WordPress ne peut alors pas court-circuiter la requête avant l'exécution du corps de la fonction, et certains comportements liés au cache ou aux en-têtes de réponse deviennent incohérents.

La deuxième erreur consiste à copier-coller un callback de permission d'une route à l'autre sans l'adapter. Une capacité pertinente pour une route de lecture ne l'est pas forcément pour une route de suppression : `edit_posts` ne devrait jamais suffire à valider une route `DELETE`, par exemple, où `delete_others_posts` ou une vérification par propriétaire est plus appropriée.

## Tester ses routes comme un attaquant le ferait

Le test le plus simple, et pourtant souvent oublié, consiste à appeler chaque route personnalisée en étant déconnecté, avec un compte abonné basique, puis avec le rôle attendu. C'est un test rapide avec `curl` ou un client REST, qui révèle en quelques minutes les routes trop permissives.

```
# Sans authentification
curl https://exemple.fr/wp-json/monplugin/v1/commandes

# Avec un compte abonné
curl https://exemple.fr/wp-json/monplugin/v1/commandes \
  -u "abonne:motdepasseapplication"
```

## En résumé

La stricte obligation de renseigner `permission_callback` depuis WordPress 5.5 n'est pas une contrainte administrative supplémentaire, c'est un garde-fou qui force à se poser la bonne question au moment où elle compte le plus : au moment d'écrire la route. Un `permission_callback` pensé pour chaque cas d'usage, plutôt qu'un `__return_true` automatique, reste le meilleur moyen d'éviter qu'une route pratique en développement ne devienne une fuite de données en production.
