ADR-0014 — Cookies isolés par app (host-only), token distinct par app, pas de SSO implicite¶
Date : 2026-06-17
Catégorie : A (convention de sécurité transverse, dans le cadre de l'auth déjà validée)
Décideur : Lead architecte DYNORS
Statut : Acceptée
S'appuie sur : [[0011-jwt-audience-realm-scoping]] (un aud par app), [[0013-jwt-asymetrique-kid-jwks-rotation]] (RS256 + JWKS)
Implémentation :
- Source unique com.dynors.commons.web.DynorsAccessCookie (dynors-commons 1.0.9) — noms de base + dérivation des préfixes __Host-/__Secure-. Émetteur et valideur en dépendent (plus de constante recopiée).
- dynors-platform/auth — AuthController (cookies via DynorsAccessCookie), CookieProductionReadinessValidator (fail-fast prod si cookie-domain posé) ; dépend maintenant de dynors-commons.
- core/security 1.0.9 — JwtAuthFilter lit le token depuis le cookie host-only (SecurityProperties.JwtProperties.cookie-enabled|name|secure, effectiveCookieName() via DynorsAccessCookie), priorité au header Bearer.
- SIRRAT Forge / Codex — TAIL_AUTH_COOKIE_NAME (défaut sid, nom neutre anti-fingerprinting), TAIL_AUTH_COOKIE_SECURE, TAIL_AUTH_COOKIE_ENABLED (changeset 017-sirrat-codex-cookie-isolation).
Contexte¶
La question posée : un cookie de session posé sur .dynors.com est renvoyé par le
navigateur à tous les sous-domaines (api.supergest.dynors.com, sly.dynors.com…).
C'est la mécanique classique du SSO « cookie de domaine parent ». Pratique, mais cela revient
à faire confiance implicitement à toutes les apps du domaine : le même cookie circule
partout, le rayon de souffle d'un sous-domaine compromis (XSS, sous-domaine oublié, takeover)
couvre toute la plateforme.
DYNORS a pour posture « la menace ne vient pas forcément de nous » et vise une surface minimale et indevinable. Partager un cookie entre apps contredit cette posture.
Décision¶
- Cookie host-only par app. En prod, l'access cookie est posé avec le préfixe
__Host-(forceSecure,Path=/, interdit l'attributDomain→ le cookie est lié à l'hôte exact et n'est jamais envoyé à un autre sous-domaine). Le refresh utilise__Secure-(sonPathrestreint/auth/refreshest incompatible avec__Host-qui exigePath=/). AucunDomainn'est jamais posé ;dynors.auth.cookie-domainest interdit en prod (le validateur échoue au démarrage s'il est renseigné).
Le nom de base est neutre (sid / rid) : il ne révèle ni la marque (« dynors ») ni la
nature (« token ») — réduit le fingerprinting et la reconnaissance passive. Identique pour toutes
les apps (sans risque : cookies host-only, jamais partagés). Le nom n'est pas un secret ; la vraie
barrière reste HttpOnly + Secure + SameSite + __Host- + validation aud.
-
Un token distinct par app. Chaque token porte son propre
aud(= tech code de l'app, ADR-0011). Même si un cookie « fuyait » vers un autre hôte, le token serait rejeté par la validation d'audience de l'app cible. Cookie ≠ autorisation : l'accès est scopé paraud. -
Tout le monde s'authentifie. Aucune app n'hérite d'une session « déjà ouverte ailleurs ». Chaque app authentifie l'utilisateur — LDAP (interne) ou auth simple (B2C/B2B) — et reçoit son token. Pas de propagation automatique.
-
SSO interne = échange explicite (pas implicite). Pour les apps internes, un SSO est prévu mais via un portail (SLY Portal,
PortalTokenService) qui détient la session sur son propre hôte et émet à la demande un token court scopé pour l'app cible (token exchange). Jamais un cookie de domaine parent partagé. Ainsi l'utilisateur ne ressaisit pas ses identifiants, mais chaque app reçoit un token qui lui est propre et révocable.
Conséquences¶
Positif : isolation maximale (un sous-domaine compromis ne livre pas les autres) ; révocation
par app ; aucune confiance implicite ; cohérent avec aud per-app et la posture « surface minimale ».
Coût : pas de SSO « silencieux » par simple cookie ; le confort SSO interne passe par le
portail (token exchange) à implémenter (palier 2). En dev local (http), les préfixes
__Host-/__Secure- exigeant HTTPS sont désactivés (secure-cookie=false) → noms de
cookies nus ; la vraie politique s'applique dès qu'on est en HTTPS (env/prod).
Répercuté ✅ : le lecteur (dynors-security JwtAuthFilter) et l'émetteur (dynors-auth) dérivent
le nom du cookie de la même source DynorsAccessCookie — divergence de préfixe impossible. Le
réglage commun secure est piloté côté Forge par TAIL_AUTH_COOKIE_SECURE.
Alternatives écartées¶
- Cookie domaine-large
.dynors.com+audstrict : SSO simple, mais rayon de souffle large et confiance implicite entre apps → contraire à la posture. Écartée. - Tout host-only sans aucun SSO : retenu comme base ; le confort SSO interne est ajouté par le portail (échange explicite), sans renoncer à l'isolation.
Références¶
docs/security/SURFACE_ATTAQUE_FRONT_IDENTITE_2026-06-14.md(§ cookies).cursor/rules/entitlements-auth-erreurs-conventions.mdc- ADR-0011 (audience per-app), ADR-0013 (asymétrique), ADR-0008 (transit SLY par app)