# Authentification headless : cookies, nonces et les limites du CORS

> Cookies WordPress et nonces suffisent pour un frontend same-origin, mais montrent vite leurs limites face à un vrai site headless distant. Explications.

- Auteur : Clément Hadrot
- Publié le : 2020-04-22
- Mis à jour le : 2020-04-22
- Catégorie : Headless &amp; API
- URL : https://wpmoderne.dev.wordpress-developpement.fr/headless/authentification-headless-cookies-nonces-cors/

## L’essentiel

- X-WP-Nonce protège les requêtes authentifiées contre le CSRF
- rest_cookie_check_errors valide la session côté serveur
- Le CORS bloque par défaut les appels cross-origin authentifiés

Avant de se tourner vers des solutions comme les mots de passe d'application ou JWT, il est utile de comprendre la méthode d'authentification historique de l'API REST WordPress : les cookies de session combinés à un nonce. Cette méthode fonctionne parfaitement dans un cas précis, et échoue methodiquement dans un autre. Comprendre pourquoi évite bien des heures de débogage.

Le cas qui fonctionne : un thème classique qui charge du JavaScript sur le même domaine que l'administration WordPress, et qui a besoin d'appeler l'API REST pour, par exemple, un formulaire de recherche en direct ou un bouton « J'aime ». Le cas qui échoue : un frontend headless hébergé sur un domaine ou un port différent, qui tente de s'authentifier en tant qu'utilisateur pour écrire du contenu.

## Comment fonctionne l'authentification par cookie

Quand un visiteur est connecté à WordPress, son navigateur porte un cookie de session. Ce cookie suffit, en théorie, à authentifier les requêtes REST envoyées depuis le même navigateur. Mais WordPress ajoute une protection supplémentaire obligatoire : le nonce (*number used once*), un jeton anti-CSRF généré côté serveur et valable une durée limitée, douze heures par défaut.

Côté PHP, on génère ce nonce avec `wp_create_nonce( 'wp_rest' )`, généralement injecté dans le HTML de la page via `wp_localize_script()` :

```
wp_localize_script( 'mon-script', 'wpApiSettings', array(
    'root'  => esc_url_raw( rest_url() ),
    'nonce' => wp_create_nonce( 'wp_rest' ),
) );
```

Côté JavaScript, ce nonce doit accompagner chaque requête dans l'en-tête `X-WP-Nonce` :

```
fetch( wpApiSettings.root + 'wp/v2/posts', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-WP-Nonce': wpApiSettings.nonce,
    },
    body: JSON.stringify( { title: 'Nouvel article', status: 'draft' } ),
} );
```

## La vérification côté serveur

WordPress valide automatiquement ce nonce via la fonction interne `rest_cookie_check_errors()`, accrochée au filtre `rest_authentication_errors`. Si le nonce est absent, expiré, ou ne correspond pas à l'utilisateur du cookie, la requête est rejetée avec une erreur 401, quand bien même le cookie de session serait valide. C'est une sécurité volontaire : le cookie seul ne suffit jamais, il faut toujours le nonce en complément.

> L'essentiel à retenir : X-WP-Nonce protège les requêtes authentifiées contre le CSRF ; rest_cookie_check_errors valide la session côté serveur ; Le CORS bloque par défaut les appels cross-origin authentifiés

## Le mur du CORS pour un vrai headless

Le problème apparaît dès que le frontend n'est plus servi depuis le même domaine que WordPress. Un site Next.js hébergé sur `mon-site.fr` qui interroge une API WordPress sur `api.mon-site.fr` constitue déjà une requête cross-origin. Or, par défaut, les navigateurs bloquent les requêtes cross-origin qui envoient des cookies, sauf configuration explicite du *Cross-Origin Resource Sharing* (CORS).

Il est possible de forcer l'envoi des en-têtes CORS nécessaires via le filtre `rest_pre_serve_request`, en ajoutant manuellement `Access-Control-Allow-Origin`, `Access-Control-Allow-Credentials` et les en-têtes autorisés :

```
add_filter( 'rest_pre_serve_request', function( $served, $result, $request ) {
    header( 'Access-Control-Allow-Origin: https://mon-site.fr' );
    header( 'Access-Control-Allow-Credentials: true' );
    header( 'Access-Control-Allow-Headers: X-WP-Nonce, Content-Type' );
    return $served;
}, 10, 3 );
```

Techniquement, cela peut fonctionner. Mais dans la pratique, cette approche pose plusieurs problèmes pour un vrai projet headless :

- Le cookie de session doit rester accessible malgré les restrictions de plus en plus strictes des navigateurs sur les cookies tiers
- Le nonce expire au bout de douze heures, ce qui oblige à rafraîchir la session régulièrement côté frontend
- Cette authentification suppose que l'utilisateur ait un compte WordPress et se connecte via l'interface d'administration, un scénario rarement adapté à une application cliente autonome

## Pour quel usage cette méthode reste pertinente

Cookies et nonces restent le bon choix pour un scénario same-origin : un thème classique enrichi de composants JavaScript interactifs, un plugin qui ajoute une interface d'administration personnalisée, ou tout script chargé directement par WordPress. Dans ces cas, aucune configuration CORS n'est nécessaire, et la sécurité fournie par le nonce est largement suffisante.

> Sur nos projets, la règle est simple : cookies et nonce pour tout ce qui reste dans le périmètre du domaine WordPress, autre chose dès qu'un frontend distant entre en jeu. Mélanger les deux approches finit toujours par créer des bugs difficiles à reproduire en local.

## En résumé

L'authentification par cookie et nonce est robuste, bien intégrée et suffisante pour de nombreux usages, mais elle n'a pas été conçue pour un frontend headless véritablement découplé. Pour ce cas de figure, WordPress propose depuis la version 5.6 une solution native bien plus adaptée : les mots de passe d'application, que nous détaillerons dans un prochain article. En attendant, retenez ceci : si votre frontend vit sur un autre domaine que votre WordPress, ne partez pas sur cookies et nonce pour l'authentification en écriture.
