Gestion des Tokens OAuth dans une SPA avec le Pattern BFF
Lorsqu'on gère OAuth dans une application monopage (SPA), l'emplacement de stockage des tokens d'accès et d'actualisation fait débat depuis longtemps. Le localStorage, le sessionStorage et les variables en mémoire sont tous insuffisants face aux attaques XSS. Le pattern Backend for Frontend (BFF) est une conception où les tokens sont conservés côté serveur plutôt que transmis au navigateur. Cet article présente le mécanisme et les points clés de mise en œuvre.
Prérequis
Cet article suppose l'environnement suivant :
- Développement d'une application basée sur un navigateur utilisant OAuth 2.0 et OpenID Connect.
- Existence d'une SPA, des API qu'elle appelle et d'un serveur d'autorisation.
- La SPA et ses composants serveur associés peuvent être placés sur le même domaine parent.
- HTTPS est un prérequis (nécessaire pour l'émission de cookies sécurisés).
Risques XSS dans les Applications Basées sur un Navigateur
Le code d'application exécuté dans le navigateur est vulnérable à tout ce qui se produit dans l'environnement d'exécution du navigateur. Les XSS ont un impact étendu, et comme le code d'attaque s'exécute dans le même contexte que l'application, les opérations suivantes sont possibles :
- Lecture des valeurs stockées dans le localStorage ou le sessionStorage.
- Lecture des variables en mémoire accessibles depuis JavaScript.
- Exécution de tous les appels API que l'application peut effectuer.
- Modification du comportement en écrasant les fonctions intégrées (pollution du prototype).
Il existe plusieurs points d'entrée pour les XSS, tels que les vulnérabilités dans les bibliothèques dépendantes, les défauts dans le traitement d'entrée/sortie de votre propre code, ou la compromission de scripts tiers. Comme il est difficile de prévenir complètement les XSS, une politique réaliste consiste à « limiter l'impact en cas d'intrusion ».
Tant que les tokens sont placés dans le navigateur, la possibilité qu'ils soient volés via XSS subsiste. Si un attaquant utilise un token d'actualisation volé dans son propre environnement, il peut appeler les API pendant longtemps même après que l'utilisateur a fermé le site web. Bien que la rotation des tokens et les délais d'inactivité puissent atténuer l'impact, ils ne constituent pas des solutions fondamentales.
Conception Sans Placement des Tokens dans le Navigateur
Le pattern BFF fournit un composant serveur dédié à la SPA et centralise les responsabilités du client OAuth. La SPA ne gère pas directement le traitement OAuth, mais effectue l'authentification et les appels API via le BFF.
La répartition des rôles est la suivante :
- Communication du protocole OAuth avec le serveur d'autorisation : gérée par le BFF.
- Conservation des tokens d'accès et d'actualisation : BFF uniquement.
- Maintien de l'état d'authentification entre la SPA et le BFF : Cookie HttpOnly.
- Appels API : la SPA envoie des requêtes au BFF, et le BFF convertit le Cookie en token et le transmet à l'API.
Dans cette configuration, les tokens circulent uniquement entre le BFF et les API qu'il appelle, et sont invisibles pour le JavaScript du navigateur. Même en cas de XSS, un attaquant ne peut pas extraire le token et l'utiliser depuis un autre endroit. Tout ce qu'un attaquant peut faire est d'envoyer des requêtes au BFF dans le cadre de la session actuellement ouverte par l'utilisateur. Bien que cet impact ne soit pas négligeable, sa durée et sa portée sont considérablement limitées par rapport au vol de token.
Le BFF agit comme ce que la terminologie OAuth appelle un « client confidentiel ». Il détient un secret client et effectue entièrement côté serveur l'échange des codes d'autorisation contre des tokens et le traitement d'actualisation.
Flux d'Authentification
Un flux d'authentification typique est le suivant :

- Lorsque la SPA lance une connexion, elle envoie une demande de connexion au BFF.
- Le BFF démarre un flux de code d'autorisation avec PKCE, génère une URL de redirection vers le serveur d'autorisation et la renvoie à la SPA.
- La SPA redirige le navigateur vers cette URL, et l'utilisateur s'authentifie auprès du serveur d'autorisation.
- Le serveur d'autorisation renvoie un code d'autorisation à l'URI de redirection du BFF.
- Le BFF échange le code d'autorisation contre des tokens et obtient un token d'accès et un token d'actualisation.
- Le BFF stocke les tokens dans sa propre zone sécurisée (cookie chiffré, magasin de sessions côté serveur, etc.) et renvoie uniquement un identifiant de session à la SPA via un Cookie HttpOnly.
- Lorsque la SPA appelle une API, elle envoie la requête via le BFF. Le Cookie est envoyé simultanément, et le BFF le convertit en token d'accès et le transmet à l'API en amont.
- Si le token d'accès expire, le BFF le met à jour silencieusement à l'aide du token d'actualisation.
Du point de vue de la SPA, l'état de connexion est maintenu par le Cookie, et les requêtes API sont effectuées avec des appels fetch normaux. Aucun code gérant directement les tokens OAuth ou les codes d'autorisation n'existe dans la SPA.
Paramètres de Sécurité Requis
Les attributs suivants doivent être définis pour le Cookie émis par le BFF :
- HttpOnly : Interdit l'accès depuis JavaScript. Même avec XSS, le contenu du Cookie ne peut pas être lu.
- Secure : Envoyé uniquement via HTTPS.
- SameSite=Strict : Garantit que le Cookie n'est pas envoyé avec des requêtes provenant d'autres sites. Cela aide à bloquer les principales voies d'attaque CSRF, mais la protection CSRF n'est pas complète avec cela seul.
- Préfixe __Host- : L'ajout du préfixe __Host- au nom du Cookie garantit que le navigateur limite le Cookie à l'hôte émetteur et ne le partage pas avec les sous-domaines.
Si les tokens sont stockés dans un Cookie chiffré (session côté client), le contenu du Cookie est chiffré. Si les tokens sont placés dans un magasin de sessions côté serveur (session côté serveur), le Cookie ne contient qu'un identifiant de session, donc le chiffrement n'est pas nécessaire.
La Protection CSRF Ne Doit Pas Reposer Uniquement sur SameSite
Comme elle utilise une authentification basée sur les Cookies, la BFF doit implémenter une protection contre les CSRF. SameSite=Strict est une étape valide, mais pas la solution complète. Une attention particulière est nécessaire dans les configurations où la SPA est placée sur www.example.com et la BFF sur api.example.com. Étant donné que la détermination SameSite se fait par site plutôt que par origine, les requêtes provenant d'autres sous-domaines sous example.com sont considérées comme « même site », et les Cookies seront envoyés même avec SameSite=Strict.
Par conséquent, renforcez la défense CSRF avec l'une des méthodes suivantes :
- Si la BFF et la SPA sont sur des origines différentes, utilisez CORS et la validation de l'en-tête Origin pour la défense.
- Pour les requêtes qui modifient l'état, vérifiez les tokens CSRF à l'aide de la méthode anti-falsification / double soumission de cookie.
Limiter CORS aux Origines Exactes
CORS ne doit être autorisé que pour l'origine exacte de la SPA. Pour les requêtes avec identifiants impliquant des Cookies, les navigateurs n'autorisent pas les caractères génériques (*) dans Access-Control-Allow-Origin. Par conséquent, la BFF doit refléter l'origine exacte de la SPA dans la réponse. Cette limitation stricte des origines autorisées fait partie de la défense CSRF.
Une gestion des clés est nécessaire pour les données de session détenues par la BFF, en particulier les informations de token stockées dans des Cookies chiffrés. Intégrez des opérations pour injecter de manière sécurisée les clés en tant que paramètres de la BFF et les faire pivoter régulièrement.
Notez que placer la SPA et la BFF sur le même domaine parent est nécessaire pour satisfaire la condition permettant aux Cookies Same-Site de fonctionner comme des Cookies first-party.
Évolution : BFF Piloté par API
Si une BFF est construite comme une application web traditionnelle, le rendu côté serveur de la BFF s'implique dans les transitions de page de la SPA. Une BFF pilotée par API minimise cet impact.
Les rôles sont divisés en deux :
- Agent OAuth : Une API responsable du traitement du protocole OAuth. Elle est appelée depuis la SPA via JSON.
- Proxy OAuth : Fonctionne comme un plugin de passerelle API, extrait le token du Cookie et le transmet à l'API en amont.
Dans cette configuration, la SPA appelle simplement l'Agent OAuth comme une API REST normale. L'expérience de développement frontend de la SPA peut être maintenue presque identique à celle d'avant l'introduction de la BFF.
Considérations pour l'Adoption
Lors de l'adoption du pattern BFF, tenez compte des points suivants :
- Des composants architecturaux supplémentaires augmentent les coûts de développement et d'exploitation.
- La SPA et la BFF doivent être placées sur le même domaine parent.
- Une configuration qui transmet simplement l'en-tête Authorization via un proxy inverse n'est pas une BFF. L'essence d'une BFF est de détenir le token en tant que client confidentiel.
- PKCE et BFF sont utilisés ensemble, pas comme des alternatives.
- La BFF doit vérifier l'hôte de destination avant de transmettre pour éviter l'exposition du token à des hôtes non intentionnés.
- La conception de la déconnexion devient complexe car les sessions SPA, les cookies BFF et les sessions du serveur d'autorisation doivent tous être liés.
Résumé
Une façon pratique de gérer les tokens en toute sécurité dans les applications basées sur un navigateur est de ne pas les placer dans le navigateur. Le pattern BFF déplace les responsabilités du client OAuth côté serveur et ne transmet que des Cookies HttpOnly au navigateur. Cela empêche le vol de token même en cas de XSS. En divisant les rôles en un Agent OAuth et un Proxy OAuth, la sécurité peut être renforcée tout en maintenant l'expérience de développement de la SPA.





