Cookies ou Bearer tokens : le premier choix
C'est la première bifurcation. Les deux mécanismes fonctionnent avec Drupal, mais leurs implications sont très différentes.
Cookie de session : Drupal crée une session côté serveur et envoie un cookie au navigateur. Simple à mettre en place, familier pour les équipes PHP. Le problème : le navigateur envoie ce cookie automatiquement à chaque requête cross-origin, ce qui ouvre la porte aux attaques CSRF. Une couche de protection supplémentaire devient nécessaire (voir la section suivante). Par ailleurs, credentials: 'include' est obligatoire côté fetch, ce qui contraint la configuration CORS : l'origine doit être explicitement listée, * est interdit.
Bearer token (OAuth2/JWT) : le client obtient un access token via le module Simple OAuth de Drupal, puis l'envoie dans l'en-tête Authorization: Bearer <token>. Le navigateur n'envoie pas cet en-tête automatiquement : le CSRF disparaît structurellement. La contrepartie : stocker le token de façon sécurisée sur le client et gérer son cycle de vie (expiration, renouvellement).
La règle de base : choisissez OAuth2/Bearer pour toute application mobile ou SPA exposée à des utilisateurs non authentifiés. Le cookie de session reste utilisable pour des contextes intranet où le front-end partage le même domaine que Drupal.
La gestion du CSRF en contexte headless
Si vous utilisez les cookies de session, Drupal expose un mécanisme natif : le token CSRF.
- Le client effectue une requête
GET /session/token. - Drupal retourne un token opaque lié à la session active.
- Ce token doit être inclus dans l'en-tête
X-CSRF-Tokende toutes les requêtes qui modifient l'état (POST, PATCH, PUT, DELETE). - Drupal rejette toute requête de mutation sans ce token valide.
Un point souvent mal compris : ce mécanisme ne protège que les requêtes émises par un client disposant d'un cookie de session valide. Pour les requêtes non authentifiées vers des endpoints publics, il n'y a rien à protéger côté CSRF. Mais dès qu'un utilisateur se connecte et que Drupal établit une session, le token devient obligatoire sur toutes les mutations.
Avec OAuth2 Bearer, ce problème ne se pose pas : sans cookie, pas d'attaque CSRF possible par définition.
CORS : configurer sans tout ouvrir
Dans Drupal, la configuration CORS se gère dans services.yml (ou dans un fichier de surcharge de services). Quatre paramètres à ne jamais laisser à leurs valeurs par défaut :
allowedOrigins : liste explicite des origines autorisées (https://app.exemple.com, https://mobile.exemple.com). Ne mettez jamais * en production, particulièrement si vous activez allowCredentials: true. La combinaison wildcard + credentials est refusée par les navigateurs récents et constitue une faille en soi.
allowedHeaders : incluez Authorization, Content-Type et X-CSRF-Token (si vous utilisez les cookies). Sans ces en-têtes dans la liste, les requêtes preflight OPTIONS retournent 403 et vos appels API échouent côté client sans message d'erreur clair.
allowCredentials : à true uniquement si vous utilisez les cookies de session. Avec OAuth2 Bearer, laissez à false.
allowedMethods : listez explicitement GET, POST, PATCH, PUT, DELETE, OPTIONS. L'oubli de PATCH bloque les mises à jour partielles de contenu via JSON:API, erreur fréquente découverte en recette.
Piège courant en recette : le back-end Drupal est déployé derrière un proxy (nginx, Varnish, load balancer cloud) qui retire ou écrase les en-têtes CORS. Testez les en-têtes directement sur la réponse HTTP finale, pas seulement en local sur Drupal.
Durée de vie des tokens et rotation
Un access token OAuth2 a une durée de vie courte, configurable dans Simple OAuth. Quelques dizaines de minutes à une heure constitue un équilibre raisonnable : assez long pour ne pas bloquer l'utilisateur à chaque action, assez court pour limiter l'exposition si le token est intercepté.
Le refresh token dure plus longtemps (jours à semaines). Il sert uniquement à obtenir un nouvel access token sans que l'utilisateur se reconnecte. Deux règles pour sa gestion :
La rotation : à chaque utilisation d'un refresh token, Drupal (via Simple OAuth) en émet un nouveau et invalide l'ancien. Si quelqu'un a volé votre refresh token et l'utilise, la prochaine tentative de votre application réelle échoue : c'est le signal d'une compromission détectable.
Le stockage : sur un navigateur, le refresh token ne va jamais dans localStorage (accessible en JavaScript, donc vulnérable au XSS). Le patron recommandé stocke le refresh token dans un cookie httpOnly; Secure; SameSite=Strict, géré par un composant serveur (voir le patron BFF ci-dessous). Sur mobile, utilisez le keychain iOS ou le keystore Android.
Scopes OAuth2 : exposer le minimum
Simple OAuth permet de définir des scopes dans l'administration Drupal. Chaque scope correspond à un ensemble de permissions. Un client OAuth (votre front Next.js, votre application mobile, un service tiers) ne reçoit que les scopes dont il a besoin.
Exemples de scopes granulaires :
article:readpour lire des nœuds de type articlearticle:writepour créer ou modifier des articlesuser:profilepour lire le profil de l'utilisateur connectéadminpour les opérations d'administration (ne donnez jamais ce scope à un client front-end)
La logique : si un token compromis ne peut accéder qu'à article:read, l'attaquant atteint les articles publiés, pas les données utilisateurs, pas le contenu privé, pas les opérations d'écriture. Le rayon d'action d'une compromission est limité structurellement, pas par espoir.
Deux patrons d'architecture pour le contexte headless
Le BFF (Backend for Frontend)
Un serveur Node.js (souvent Next.js en mode serveur) joue le rôle d'intermédiaire OAuth :
- Le navigateur redirige vers Drupal pour l'authentification.
- Drupal retourne le code OAuth au BFF, pas au navigateur.
- Le BFF échange le code contre access token et refresh token.
- Le BFF stocke le refresh token dans un cookie
httpOnlyet proxifie les requêtes API avec l'access token. - Le navigateur ne voit jamais les tokens directement.
Ce patron règle le problème de stockage côté navigateur. Le BFF est le seul client OAuth confidentiel : il détient le client_secret, pas le front-end.
PKCE pour les SPA sans serveur intermédiaire
Si votre SPA est purement statique (pas de composant serveur), utilisez le flux Authorization Code avec PKCE (Proof Key for Code Exchange). Pas de client_secret : le client génère un code_verifier aléatoire et envoie son hash (code_challenge) à Drupal lors de la demande d'autorisation. Drupal vérifie la cohérence au moment de l'échange de code.
L'access token vit en mémoire JavaScript (variable d'état, pas de persistance), jamais dans localStorage. Durée de vie courte, pas de refresh token persistant sur le client.
Check-list de tests de sécurité à automatiser
Ces tests s'intègrent dans un pipeline CI ou dans votre suite de tests d'intégration existante :
- CORS non autorisé : une requête avec
Origin: https://site-malveillant.comdoit retourner une réponse sansAccess-Control-Allow-Origin. - CSRF manquant : une requête POST sans
X-CSRF-Token(en mode cookie) doit retourner 403. - Token expiré : une requête avec un access token délibérément expiré doit retourner 401.
- Scope insuffisant : une requête sur
/jsonapi/usersavec un tokenarticle:readdoit retourner 403. - Rotation du refresh token : après un renouvellement, l'ancien refresh token doit être rejeté (401).
- Token dans les logs : vérifiez que vos logs nginx ou Drupal ne capturent pas le contenu de l'en-tête
Authorization. - HTTP non chiffré : une requête HTTP vers un endpoint protégé doit être redirigée (301/302) ou rejetée (403), pas servie.
- Endpoints admin :
/user/loginet/admin/*doivent retourner 403 depuis une origine front-end non autorisée.
Ces huit vérifications couvrent les vulnérabilités les plus fréquentes observées sur des projets headless. Elles sont automatisables avec curl, httpie, ou Playwright selon votre stack de tests.
Ce que ça change en pratique
L'authentification dans un contexte headless n'est pas un problème qu'on règle à la fin du projet. Les choix de la première semaine (cookie ou token, BFF ou SPA pure, scopes fins ou larges) conditionnent l'architecture de sécurité pour toute la durée de vie du système.
Pour les DSI et les architectes qui cadrent un projet headless : posez ces questions dès le brief technique. Revenir sur ces fondations après livraison coûte significativement plus qu'une heure de cadrage en amont.
Si vous évaluez l'architecture d'un projet Drupal découplé et que ces décisions sont encore ouvertes, demandez un diagnostic.