1. Ce que change l'architecture découplée sur le plan de l'authentification
Dans un Drupal monolithique classique, l'authentification repose sur un cookie de session géré par Symfony : le navigateur envoie le cookie, Drupal valide la session côté serveur, tout reste en première partie (same-origin). Les mécanismes de protection CSRF de Drupal fonctionnent de manière transparente.
Dans une architecture headless, ce modèle se fragmente : le front est servi depuis un domaine distinct, les appels API transitent en cross-origin, et les sessions navigateur perdent leur pertinence dès qu'une application mobile ou un contexte SSR est impliqué.
Chaque choix d'authentification en headless est un choix de surface d'attaque. Il n'y a pas de configuration qui convienne à tous les projets — il y a des patterns adaptés à des contextes précis.
2. Cookies ou tokens ? Le choix structurant
Cookie de session httpOnly
Drupal peut émettre un cookie de session httpOnly en mode headless, à condition que le front et l'API partagent le même domaine parent. Le token n'est jamais accessible en JavaScript, mais le pattern reste incompatible avec les applications mobiles natives et impose credentials: true en CORS.
JWT via Simple OAuth
Le JWT est le choix dominant en headless pur. Drupal expose un endpoint /oauth/token via le module Simple OAuth. Le token ne peut pas être révoqué avant son expiration — une courte durée de vie (5–15 min) est impérative, associée à un refresh token stocké en cookie httpOnly et soumis à rotation.
Pattern BFF (Backend For Frontend) pour Next.js
Pour les fronts Next.js avec SSR, le BFF agit comme proxy : il récupère le token OAuth côté serveur sans exposer le client_secret au navigateur, stocke le refresh token en cookie httpOnly, et injecte l'access token dans les appels Drupal. Le navigateur ne voit jamais ni le secret, ni le refresh token.
3. CSRF en contexte headless
La protection CSRF de Drupal repose sur un token envoyé dans le header X-CSRF-Token. Toute route qui modifie un état (POST, PATCH, DELETE) doit l'exiger, obtenu préalablement via GET /session/token.
Les JWT transmis par header Authorization: Bearer réduisent le risque CSRF (un attaquant ne peut pas injecter ce header cross-origin). Mais dès qu'un cookie entre dans l'équation, le CSRF doit être adressé explicitement.
4. Configuration CORS : précision plutôt que permissivité
En production, allowedOrigins: ['*'] est une erreur structurelle. La configuration minimale recommandée liste explicitement les origines autorisées, limite les headers à ceux utilisés, et fixe un maxAge raisonnable pour le cache des preflights. CORS protège le navigateur — pas l'API côté serveur. La vérification du token et les permissions Drupal sont les vrais gardiens.
5. Gestion des secrets et rotation
- client_secret OAuth : vit uniquement côté serveur, jamais dans une variable
NEXT_PUBLIC_ni dans le dépôt Git. Rotation planifiée tous les 90 jours. - Clés de signature JWT : générées en RSA 2048 bits minimum, injectées via un gestionnaire de secrets (Vault, AWS Secrets Manager…), rotatives annuellement ou sur incident.
- Refresh token : rotation obligatoire à chaque usage, stockage httpOnly uniquement en contexte navigateur, révocation sur déconnexion via
/oauth/revoke.
6. Exposition minimale des scopes
Chaque client OAuth ne doit obtenir que les scopes strictement nécessaires. Un découpage pratique : content:read, content:write, user:profile, commerce:checkout, admin:config. Un token compromis avec un scope content:read ne permet pas d'écrire dans le CMS.
Auditez également les ressources JSON:API exposées et désactivez celles qui ne doivent pas être accessibles depuis l'extérieur.
7. Patrons d'architecture de référence
Next.js + Drupal avec BFF : le navigateur ne voit jamais le refresh token. Next.js gère l'échange OAuth côté serveur, stocke le refresh token en cookie httpOnly, et injecte l'access token dans les appels SSR vers Drupal.
Application mobile (PKCE) : le PKCE remplace le client_secret pour les apps publiques. Le code verifier est généré côté client, hashé en code challenge, et vérifié par Drupal lors de l'échange du code d'autorisation.
Machine-to-machine (client_credentials) : pas d'utilisateur dans la boucle. Le token a une courte durée de vie, pas de refresh token. Le client_secret vit dans le gestionnaire de secrets de l'infrastructure.
8. Check-list de tests de sécurité à automatiser
- Token expiré → HTTP 401
- JWT falsifié → rejet
- Refresh token après rotation → ancien token invalide
- Scope insuffisant → HTTP 403 sur route d'écriture
- Révocation → token invalide immédiatement
- Mutation sans X-CSRF-Token → HTTP 403
- Requête depuis une origine non autorisée → pas de header CORS
- Wildcard
*absent en production avec credentials - Routes JSON:API sensibles → 403 sans scope approprié
- Absence du client_secret dans le bundle JS front
- Absence de clés dans l'historique Git
- HTTPS obligatoire, HSTS activé
Conclusion
L'authentification dans un projet Drupal découplé n'est pas un problème de module — c'est un problème d'architecture. La décision cookie/token, la stratégie CSRF, la configuration CORS, la gestion des secrets et la granularité des scopes forment un système cohérent. Une seule décision mal prise compromet l'ensemble.
Si votre projet headless est en cours de conception ou si vous souhaitez auditer un système existant, demandez un diagnostic DrupaLabs.
Un projet web en tête ?
Commençons par un diagnostic : ce qui vous ralentit, et par où commencer.
Demander un diagnostic