Skip to content

Sessions web (cookie)

Les pages HTML servies par Hydrogen partagent les mêmes lignes user_session que l’API — seul le transport change : Authorization: Bearer <token> pour l’API, cookie pour le web. Pratiquement, ça veut dire :

  • Un même utilisateur peut être connecté simultanément côté site et côté app mobile, chaque session apparaîtra dans GET /api/auth/sessions.
  • La révocation d’une session web depuis l’app mobile (et inversement) fonctionne sans code spécial.
  • Le throttle anti-bruteforce est partagé : 5 échecs sur /login ou POST /api/auth/login ferment l’IP/email côté API ET côté web.
CookieHttpOnlySecure (prod)SameSitePathRôle
hydrogen_session*ouiouiLax*/Porte le bearer token de session ; sliding Max-Age aligné sur user_session.expires_at (30 jours).
csrf*nonouiStrict/Token CSRF (64 hex chars) du double-submit cookie. Lu par le JS pour être renvoyé.

* noms et politiques configurables : WEB_SESSION_COOKIE_NAME, WEB_COOKIE_SECURE, WEB_COOKIE_SAMESITE, WEB_COOKIE_DOMAIN, CSRF_COOKIE_NAME.

request → WebSession → Locale → Csrf → TwigGlobals → action
  • WebSession : lit le cookie de session, appelle SessionService::authenticate(), réémet le cookie avec le nouveau expires_at (sliding). Sur cookie invalide : passe en anonyme et efface le cookie.
  • Locale : choisit la locale (settings utilisateur si connecté, sinon Accept-Language).
  • Csrf : sur POST/PUT/PATCH/DELETE, exige soit un champ _csrf dans le corps, soit l’en-tête X-CSRF-Token. Comparaison hash_equals. Rejet 403 plain text en cas de désaccord ou de cookie absent/malformé.
  • TwigGlobals : publie viewer (objet User ou null), csrf_token (string), locale dans toutes les templates Twig.
VariableDéfautEffet
WEB_SESSION_COOKIE_NAMEhydrogen_sessionNom du cookie de session.
WEB_COOKIE_SECUREfalsetrue en prod (HTTPS). Concerne les deux cookies (session + CSRF).
WEB_COOKIE_SAMESITELaxPolitique SameSite du cookie de session. Strict si pas besoin de GET cross-site authentifié.
WEB_COOKIE_DOMAIN(vide)Attribut Domain explicite. Vide = host-only (recommandé).
CSRF_COOKIE_NAMEcsrfNom du cookie CSRF.

Le partial partials/cookie-consent.twig s’inclut à la fin du <body> de toute page web qui doit afficher le bandeau (home.twig, auth/login.twig, etc.). Il s’appuie sur la lib orestbida/cookieconsent v3 chargée depuis jsdelivr (CSS + UMD pinned au tag v3.0.1).

Catégories :

SlugDécochable ?Couvre
necessarynonhydrogen_session, csrf, le cookie de mémorisation du consentement lui-même.
analyticsouiGoogle Analytics 4 (_ga, _ga_*) — chargé UNIQUEMENT après acceptation explicite.

Légalement (RGPD/CNIL), les cookies strictement nécessaires ne requièrent PAS de consentement. La banner reste affichée pour information sur la catégorie necessary ET pour recueillir le consentement obligatoire de la catégorie analytics.

Google Analytics 4 : pilote via GOOGLE_ANALYTICS_ID (ex. G-XXXXXXX). Vide ⇒ aucun snippet GA n’est rendu. Sinon, les deux balises <script> (chargement de gtag.js + appel gtag('config', ...)) sont émises avec type="text/plain" et data-category="analytics" ; le browser ne les exécute PAS au parsing. CookieConsent v3 surveille ces nœuds et ne réécrit leur type en text/javascript qu’à partir de l’acceptation explicite de la catégorie — refus / pas de décision = GA ne se charge jamais, aucun cookie posé, aucun hit envoyé. anonymize_ip: true est forcé.

Localisation : les libellés sont stockés dans resources/lang/<locale>/cookies.php (catalogue racine partagé entre toutes les pages — pas sous pages.* puisque la banner traverse toutes les pages). Sections : consent.*, preferences.*, categories.<slug>.*.

Lien de réouverture : tout élément doté de l’attribut data-cc="show-preferencesModal" rouvre la modale (à utiliser dans un futur footer ou une page Confidentialité).

Réémission : la clé revision (entier) côté Twig est à incrémenter dès qu’une catégorie est ajoutée ou retirée — la banner sera alors réaffichée aux visiteurs qui avaient déjà répondu.

Ajouter un script soumis au consentement : le jour où un outil d’analytics est intégré, sa balise <script> doit porter type="text/plain" + data-category="analytics" (convention CookieConsent v3). Le script ne s’exécute alors qu’à partir de l’acceptation explicite.


Protection contre les redirections ouvertes

Section titled “Protection contre les redirections ouvertes”

Tous les endpoints qui consomment un paramètre return (?return=... ou champ return dans le corps) le filtrent via ReturnUrlSanitizer::pick(). Sont acceptés uniquement :

  • les chemins relatifs commençant par / (ex : /profile, /media/abc?from=home).

Sont rejetés :

  • les URL absolues (http://...).
  • les chemins protocol-relative (//evil.com/...).
  • les valeurs vides ou non-string.

En cas de rejet, fallback sur le default fourni (typiquement /).


Affiche le formulaire de connexion (Twig). Si l’utilisateur est déjà authentifié, redirige immédiatement vers ?return= (sanitisé) ou /.

  • Auth : aucune (mais redirige si déjà connecté)
  • Action : ShowLoginAction
  • Query params :
    • error (optionnel) — code d’erreur affiché par la template (missing_fields, invalid_credentials, banned, email_not_confirmed, throttled, unknown).
    • return (optionnel) — chemin où rediriger après login (sanitisé).
  • Réponse : 200 OK (HTML) ou 302 si déjà connecté.

Soumission du formulaire de connexion web. Mêmes règles métier que POST /api/auth/login (throttle, ban, email confirmé). Succès : pose le cookie de session, redirige vers ?return= ou /. Échec : redirige vers /login?error=<code>&return=<return>.

  • Auth : aucune
  • CSRF : requis (champ _csrf ou en-tête X-CSRF-Token)
  • Action : SubmitLoginAction
  • Corps (form-urlencoded) :
    • email (string, requis)
    • password (string, requis)
    • _csrf (string, requis)
    • return (string, optionnel)
  • Réponse 302 :
    • Succès : Location: <return> + Set-Cookie: hydrogen_session=...
    • Échec : Location: /login?error=<code>[&return=<return>]
  • Codes d’erreur (paramètre error du redirect) :
    • missing_fields — email ou password vide.
    • invalid_credentials — combinaison invalide.
    • banned — compte banni.
    • email_not_confirmed — email non confirmé.
    • throttled — trop de tentatives.
    • unknown — erreur inattendue (devrait être inatteignable).
  • Réponse 403 : CSRF token missing or invalid. (en cas d’échec du middleware CSRF).

Révoque la session web courante (supprime la ligne user_session) ET efface le cookie. Idempotent — un visiteur anonyme reçoit aussi un 302 /. Les autres sessions du même utilisateur (autres appareils, app mobile, API) ne sont pas affectées : utilisez POST /api/auth/logout-all pour ça.

  • Auth : facultative (l’action ne casse pas si la session est déjà absente)
  • CSRF : requis
  • Action : LogoutAction (web)
  • Corps (form-urlencoded) :
    • _csrf (string, requis)
    • return (string, optionnel)
  • Réponse 302 : Location: <return> (default /) + Set-Cookie: hydrogen_session=; Max-Age=0.

POST /auth/oauth/google, POST /auth/oauth/apple, POST /auth/oauth/facebook

Section titled “POST /auth/oauth/google, POST /auth/oauth/apple, POST /auth/oauth/facebook”

Pendants web des endpoints OAuth de l’API (POST /api/auth/oauth/*). Le navigateur a obtenu un token via le SDK JS du fournisseur (Google Identity Services / Sign in with Apple JS / Facebook JS SDK), puis le poste à l’un de ces trois endpoints. La logique métier est partagée avec l’API (OAuthLoginService) — seul le transport change : pas de JSON envelope, on ouvre une session web (cookie) et on redirige.

Le formulaire HTML est rendu par templates/auth/login.twig ; les boutons fournisseurs ne s’affichent que si l’env correspondante est non vide :

ProviderEnv requise(s)Bouton si vide ?
GoogleGOOGLE_OAUTH_CLIENT_IDmasqué
AppleAPPLE_OAUTH_WEB_CLIENT_ID + APPLE_OAUTH_WEB_REDIRECT_URImasqué
FacebookFACEBOOK_APP_IDmasqué
  • Auth : aucune
  • CSRF : requis (champ _csrf posé par le formulaire caché correspondant)
  • Actions :
  • Corps (form-urlencoded) :
    • Google : idToken (string, requis) + _csrf + return?
    • Apple : idToken (string, requis) + _csrf + return?
    • Facebook (classic) : flow=classic + accessToken + _csrf + return?
    • Facebook (limited) : flow=limited + idToken + _csrf + return?
  • Réponse 302 :
    • Succès : Location: <return> + Set-Cookie: hydrogen_session=...
    • Échec : Location: /login?error=<code>[&return=<return>]
  • Codes d’erreur (paramètre error du redirect, mappés sur pages.login.error.*) :
    • oauth_missing_token — body sans idToken/accessToken/flow valide.
    • oauth_token_invalid — vérification JWKS/Graph KO.
    • oauth_link_refused — un compte existe déjà pour cet email mais l’auto-link a été refusé (règles C3 pour Google ; toujours refusé pour Apple/Facebook — E.a strict).
    • oauth_email_missing — Facebook uniquement (D.a) : l’utilisateur a refusé la permission email.
    • banned — compte banni.

Les règles d’auto-link, de placeholder username et de bannissement sont strictement identiques à celles des endpoints API correspondants — voir POST /api/auth/oauth/google, apple, facebook plus bas.