Cookies et Variables de Session

Présentation

Les cookies et les variables de session sont des mécanismes permettant à une application web de stocker temporairement des données afin de les réutiliser ultérieurement. Leur rôle principal est de conserver des informations entre les différentes requêtes HTTP réalisées par un utilisateur, assurant ainsi une expérience utilisateur fluide et cohérente.

Les cookies sont stockés directement sur le navigateur (côté client), alors que les variables de session sont conservées sur le serveur. Cette différence influence fortement leur utilisation :

  • Les cookies conviennent à la conservation d'informations simples, souvent non sensibles, telles que la langue préférée d'un utilisateur, le thème graphique sélectionné, ou encore le consentement à un bandeau RGPD.
  • Les variables de session, quant à elles, offrent davantage de sécurité et sont préférées pour stocker des informations sensibles ou complexes, telles que les données d'authentification ou le contenu d'un panier d'achat sur un site e-commerce.

Les Cookies

Un cookie est toujours stocké côté client, dans le navigateur. Il peut cependant être créé soit par le client (avec JavaScript), soit par une instruction envoyée par le serveur (comme en PHP).

Créer les cookies depuis le serveur est une pratique courante et recommandée pour des raisons de sécurité et de compatibilité. Cela permet, par exemple, de garantir que le cookie sera envoyé même si JavaScript est désactivé (ce qui respecte la méthode de développement dite progressive enhancement). De plus, seul le serveur peut créer des cookies marqués HttpOnly, ce qui les rend invisibles côté JavaScript et donc plus résistants aux attaques XSS.

JavaScript est généralement utilisé pour créer des cookies liés à l'interface ou à des préférences visuelles (comme l'affichage sombre ou la position d'un menu), mais pour tout ce qui touche à la sécurité, aux sessions ou à la confidentialité, on privilégiera la création de cookies depuis le serveur.

Création de cookies

La fonction setcookie() envoie un en-tête HTTP au client pour lui demander de créer un cookie. Voici ses paramètres, accompagnés de leur valeur par défaut :


                    setcookie(
                        string $name,
                        string $value = "",
                        array $options = [
                            'expires' => 0,
                            'path' => "",           // Par défaut, le chemin de la page courante.
                            'domain' => "",         // Par défaut, le domaine de la page courante.
                            'secure' => false,
                            'httponly' => false,
                            'samesite' => ''        // Par défaut, comportement par défaut du navigateur.
                        ]
                    ): bool
                
  1. $name : Le nom du cookie.
  2. $value : La valeur associée au cookie.
  3. $options (optionnel) :
    • expires (optionnel) : La date d'expiration du cookie. Elle s'exprime en timestamp UNIX (nombre de secondes écoulées depuis le 1er janvier 1970).
    • path (optionnel) : Le chemin sur le serveur pour lequel le cookie est accessible. Si ce paramètre n'est pas précisé, le navigateur utilise par défaut le chemin de l'URL qui a créé le cookie. Par exemple, si un cookie est défini depuis /articles/creer-cookie.php, il ne sera envoyé qu'aux pages situées dans /articles/ et ses sous-dossiers.
    • domain (optionnel) : Cette option détermine à quels domaines le cookie sera envoyé. Par défaut, le cookie est réservé au domaine exact qui l'a créé (par exemple www.exemple.com). Si vous spécifiez un domaine plus large comme exemple.com, tous les sous-domaines (comme www.exemple.com ou admin.exemple.com) auront aussi accès à ce cookie. Cela peut être utile pour partager des informations entre plusieurs sous-domaines d'un même site.
    • secure (optionnel) : Le cookie est envoyé uniquement si la requête utilise une connexion HTTPS. Cela protège les données du cookie contre les interceptions sur des réseaux non sécurisés (ex.: Wi-Fi public). Il est recommandé d'activer cette option dès que votre site utilise un certificat SSL.
    • httponly (optionnel) : Le cookie devient inaccessible au JavaScript exécuté dans la page. Cela permet de se protéger contre les attaques de type XSS (Cross-Site Scripting), où un pirate injecte du code JavaScript dans une page pour voler les cookies d'un utilisateur. En marquant un cookie comme httponly, même du code malveillant exécuté dans la page ne pourra pas y accéder.
    • samesite (optionnel) : Cette option empêche l'envoi automatique d'un cookie lorsqu'une requête HTTP provient d'un autre site (requête dite "cross-site"). Elle a été introduite principalement pour protéger le cookie de session, qui est le plus souvent utilisé pour maintenir une connexion. Cela permet de contrer les attaques de type CSRF (Cross-Site Request Forgery), dans lesquelles un site malveillant tente de faire exécuter à distance une action non souhaitée sur un site où l'utilisateur est déjà connecté (par exemple : modifier une adresse ou envoyer un message à son insu).
      Trois configurations sont possibles :
      • Strict : Le cookie n'est envoyé que si l'utilisateur est déjà sur le site. Il n'est jamais transmis si la requête provient d'un autre site. Cela offre la meilleure protection, mais peut empêcher certaines fonctionnalités (comme rester connecté après avoir cliqué sur un lien depuis un autre site).
      • Lax : Le cookie est envoyé lorsque l'utilisateur est déjà sur le site. Contrairement au mode Strict, il est aussi transmis depuis un autre domaine mais uniquement lors d'une navigation GET de premier niveau. C'est-à-dire une action où l'utilisateur clique sur un lien et charge votre page dans l'onglet principal, comme s'il saisissait lui-même l'adresse dans la barre du navigateur. Il reste bloqué dans tous les autres cas provenant d'un autre domaine, comme les formulaires externes, les iframes, les images, les scripts ou les requêtes fetch exécutées depuis un site tiers.
      • None : Le cookie est envoyé dans toutes les requêtes, y compris celles venant d'autres sites ou chargées dans des iframes. Cela autorise les usages cross-site, mais n'est accepté que si le cookie est marqué secure (transmis uniquement via HTTPS). Ce mode doit être utilisé avec prudence.

Créer un cookie de session

Un cookie de session est un cookie non persistant, c'est-à-dire qu'il ne possède pas de date d'expiration. Il est automatiquement supprimé à la fermeture du navigateur. Pour en créer un, il suffit de ne pas spécifier de date d'expiration lors de l'appel à setcookie() :


                    <?php
                    // Créer un cookie de session.
                    setcookie('nomDuCookie', 'valeurDuCookie');
                    ?>
                

Créer un cookie persistant

Pour qu'un cookie reste disponible même après la fermeture du navigateur, il faut lui attribuer une date d'expiration dans le futur. Pour cela, on utilise la fonction time(), qui retourne le nombre de secondes écoulées depuis le 1er janvier 1970 (appelé timestamp UNIX). On peut alors y ajouter une durée pour fixer la date d'expiration. Par exemple, pour créer un cookie qui expirera dans une semaine :


                    <?php
                    // Définir une durée de 7 jours (60 secondes x 60 minutes x 24 heures x 7 jours).
                    $expiration = time() + 60 * 60 * 24 * 7;

                    // Créer un cookie persistant qui expirera automatiquement dans 7 jours.
                    setcookie('nomDuCookie', 'valeurDuCookie', [
                        'expires' => $expiration
                    ]);
                    ?>
                

Lecture de cookies

Pour accéder aux cookies côté serveur, on utilise la superglobale $_COOKIE. Il s'agit d'un tableau associatif contenant tous les cookies que le navigateur a renvoyés au serveur lors de la requête HTTP en cours. Chaque cookie est accessible par son nom, comme dans n'importe quel tableau associatif en PHP :


                    <?php
                    $nomDuCookie = $_COOKIE['nomDuCookie'];
                    ?>
                

Avant de lire un cookie, il est conseillé de vérifier s'il existe pour éviter une erreur de type Warning si la clé n'est pas présente. On peut le faire à l'aide de l'opérateur de coalescence nulle (??) :


                    <?php
                    $nomDuCookie = $_COOKIE['nomDuCookie'] ?? '';
                    ?>
                

Suppression de cookies

En PHP, il n'existe pas de fonction spécifique pour supprimer un cookie. Pour demander au navigateur de le retirer, on crée simplement un nouveau cookie du même nom antidaté (date d'expiration située dans le passé).

Exemple simple :


                    <?php
                    // Demande au navigateur de supprimer le cookie (date d'expiration dans le passé).
                    setcookie('nomDuCookie', '', [
                        'expires' => time() - 3600,
                        'path' => '/'
                    ]);
                    ?>
                

Attention : pour que la suppression fonctionne correctement, les paramètres path et domain doivent être identiques à ceux utilisés lors de la création du cookie. Sinon, le navigateur considérera qu'il s'agit d'un autre cookie et ne supprimera rien.

Par exemple, si un cookie a été créé avec 'path' => '/articles/', il ne pourra être supprimé que depuis une page située dans ce même sous-dossier (ou l'un de ses enfants). Si vous essayez de le supprimer depuis la racine du site (/), PHP ne verra pas ce cookie, et la suppression échouera silencieusement.

Enfin, notez que supprimer un cookie avec setcookie() ne supprime pas immédiatement sa valeur dans la superglobale $_COOKIE. PHP continue à voir l'ancienne valeur jusqu'au prochain rechargement de page. Pour éviter toute incohérence dans le script en cours, vous pouvez supprimer manuellement l'entrée :


                    <?php
                    // Demande au navigateur de supprimer le cookie (date d'expiration dans le passé).
                    setcookie('nomDuCookie', '', [
                        'expires' => time() - 3600,
                        'path' => '/'
                    ]);

                    // Retirer aussi le cookie côté PHP (sinon il reste présent jusqu'à la prochaine requête).
                    unset($_COOKIE['nomDuCookie']);
                    ?>
                

Exercices - Part 01

Exo-gestion-des-cookies-01 : Création, configuration et suppression des cookies

Objectif : Se familiariser avec les différents paramètres de configuration des cookies. En situation réelle, les cookies sont généralement créés en réponse à une action utilisateur (clic sur un bouton, envoi de formulaire, etc.). Mais pour mieux comprendre leur configuration sans complexité inutile, nous allons ici simplement effectuer les actions de création et de suppression en visitant des pages dédiées.

Instructions :

  1. Créez l'arborescence suivante en respectant la structure des dossiers et fichiers :
    
                            📁 exo-gestion-des-cookies-01/
                            ├── 📁 config/
                            │   └── 📄 constants.php
                            ├── 📁 controllers/
                            │   ├── 📄 createCookieController.php
                            │   ├── 📄 deleteCookieController.php
                            │   └── 📄 homeController.php
                            ├── 📁 demo-path/
                            │   ├── 📄 create-cookies.php
                            │   ├── 📄 delete-cookies.php
                            │   └── 📄 index.php
                            ├── 📁 template/
                            │   ├── 📄 header.php
                            │   └── 📄 footer.php
                            ├── 📄 create-cookies.php
                            ├── 📄 delete-cookies.php
                            └── 📄 index.php
                            
  2. Configuration :
    • Dans constants.php, définir la constante BASE_URL selon l'environnement (vide '' si le projet est situé à la racine, sinon le chemin vers le projet, ex.: '/mon-projet').
  3. Templates :
    • header.php : structure HTML de base avec menu dynamique via BASE_URL.
    • footer.php : fermeture des balises.
  4. Pages HTML : chaque page (racine + demo-path) contient un <h1> distinct.
  5. Contrôleurs :
    • createCookiesController.php :
      • Déclarer deux variables :
        • $pageTitle correspond au nom de la page.
        • $cookiesStatus désigne un tableau vide utilisé pour déterminer si un cookie a été créé pendant la requête HTTP actuelle ou s'il était déjà présent.
      • Créer les cookies suivants s'ils n'existent pas encore et leur attribuer la valeur de votre choix.
        • username : cookie de session (sans préciser le path).
        • age : cookie persistant (30 jours) (sans préciser le path).
        • height : cookie de session, portée limitée au chemin /demo-path/.
      • Actualiser le tableau associatif $cookiesStatus pour chaque cookie créé (clé = nom du cookie et valeur = true si vient d'être créé, false si déjà créé).
    • deleteCookiesController.php :
      • Déclarer deux variables :
        • $pageTitle correspond au nom de la page.
        • $cookiesStatus désigne un tableau vide utilisé pour déterminer si un cookie a été supprimé pendant la requête HTTP actuelle ou s'il était inexistant.
      • Supprime les cookies username, age et height si existants.
      • Met à jour $cookiesStatus pour refléter l'état de suppression (clé = nom du cookie et valeur = true si le cookie a bien été supprimé, false s'il n'existait pas au moment de la suppression).
    • homeController.php : définit seulement $pageTitle.
  6. Vues :
    • Importent leur contrôleur, le header, le footer.
    • Situées à la racine :
      • create-cookies.php :
        • Importer le contrôleur creerCookiesController.php.
        • Boucler sur $cookiesStatus et afficher un message pour chaque cookie ({nom du cookie} créé OU {nom du cookie} déjà créé).
      • delete-cookies.php :
        • Importer le contrôleur deleteCookiesController.php.
        • Boucler sur $cookiesStatus et affiche un message pour chaque cookie ({nom du cookie} supprimé OU {nom du cookie} inexistant).
      • index.php : affiche "Bonjour {firstname}, vous avez {age} ans et vous mesurez {height}", ou valeurs par défaut si cookies absents.
    • Les fichiers du dossier demo-path/ ne contiennent aucun code de traitement. Ils doivent simplement inclure la vue du même nom située à la racine. Le but est de tester l'effet du chemin d'accès sur le comportement des cookies selon le paramètre path.
  7. Tests :
    • Création et lecture des cookies depuis différents chemins (à la racine et dans le dossier "demo-path").
    • Ouvrir les outils de développement du navigateur (onglet Application > Cookies) pour observer les cookies créés, leur nom, leur valeur, leur chemin (path), leur durée de vie (expires) et autres propriétés (HttpOnly, Secure...).
    • Effacement depuis racine vs sous-dossier, mise en évidence du comportement du paramètre path.
    • Après les premiers test, ajout explicite de 'path' => '/' pour assurer la portée globale aux cookies username et age.

Les Variables de Session

Une session permet de conserver des données du côté serveur entre plusieurs requêtes HTTP d'un même utilisateur. Contrairement aux cookies, ces données ne sont ni visibles ni modifiables par le navigateur, ce qui en fait une solution plus fiable pour stocker des informations sensibles.

Les données stockées dans une session sont appelées variables de session. Elles sont créées par le serveur et associées à un identifiant unique, que le navigateur conserve dans un cookie spécial. Tant que la session reste active, ces données restent accessibles sur l'ensemble du site, peu importe la page visitée.

Les variables de session sont particulièrement utiles pour mémoriser, par exemple, l'état d'une connexion, le contenu d'un panier ou des préférences propres à un utilisateur.

Initialiser la session

Avant de pouvoir utiliser des variables de session en PHP, il faut activer la gestion de session à l'aide de la fonction session_start(). Cette fonction doit impérativement être appelée au tout début du script, avant tout affichage HTML (même une ligne vide).


                    <?php
                    // Démarrer la session (à faire avant tout affichage HTML).
                    session_start();
                    ?>
                

Pour éviter d'exécuter session_start() plusieurs fois et provoquer une erreur Warning, il est recommandé de vérifier si une session est déjà active avec session_status(). Cette fonction retourne l'état de la session en cours. Si aucune session n'est active (PHP_SESSION_NONE), on peut alors en démarrer une en toute sécurité :


                    <?php
                    // Démarrer la session si ce n'est pas déjà fait.
                    if (session_status() === PHP_SESSION_NONE) 
                    {
                        session_start();
                    }
                    ?>
                

Lorsqu'un utilisateur accède à une page qui appelle session_start(), PHP vérifie si un cookie de session (nommé PHPSESSID par défaut) est présent dans la requête. S'il est absent, PHP génère un nouvel identifiant unique et le transmet au navigateur via un cookie. S'il est présent, PHP réutilise l'identifiant contenu dans le cookie pour retrouver les données de session stockées côté serveur (par défaut dans un fichier temporaire). Ces données sont alors accessibles via la variable $_SESSION.

Enregistrer et lire des données avec les variables de session

Une fois la session démarrée, il est possible d'enregistrer des données dans le tableau associatif $_SESSION. Ces informations ne sont jamais envoyées au navigateur, elles restent sur le serveur et sont accessibles uniquement dans vos scripts PHP. Cela les rend particulièrement adaptées au stockage de données sensibles, comme l'identifiant d'un utilisateur connecté.

Par exemple, après qu'un utilisateur s'est authentifié, on peut mémoriser son identifiant et son prénom dans la session :


                    <?php
                    if (session_status() === PHP_SESSION_NONE) 
                    {
                        session_start();
                    }

                    // Ces données viendraient normalement d'une base de données.
                    $_SESSION['utilisateurId'] = 42;
                    $_SESSION['prenom'] = 'Claudy';
                    ?>
                

Ces informations restent disponibles d'une requête HTTP à l'autre tant que la session reste active. Elles permettent par exemple de contrôler l'accès à une page protégée, comme un espace membre, ou de retrouver les données de l'utilisateur issues de la base de données pour les afficher sur la page profil.


                    <?php
                    if (session_status() === PHP_SESSION_NONE) 
                    {
                        session_start();
                    }

                    // Vérifier si l'utilisateur est connecté
                    if (isset($_SESSION['utilisateurId']))
                    {
                        $id = $_SESSION['utilisateurId'];

                        // Exemple de requête : SELECT email, nom, prenom FROM utilisateurs WHERE id = $id
                    }
                    ?>
                

Suppression des variables de session

Il est possible de supprimer une ou plusieurs variables de session sans pour autant mettre fin à la session elle-même. Cela permet de nettoyer des données devenues inutiles tout en conservant la session active.

Comme $_SESSION est un tableau associatif, vous pouvez supprimer une variable précise à l'aide de la fonction unset() :


                    unset($_SESSION['nomVariable']);
                

Pour supprimer toutes les variables enregistrées dans la session d'un coup, vous pouvez utiliser la fonction session_unset(). Celle-ci vide entièrement le tableau $_SESSION (équivalent à $_SESSION = []), mais ne détruit pas la session elle-même. L'identifiant de session reste actif, et le cookie PHPSESSID est conservé dans le navigateur.


                    session_unset();
                

Sécuriser la session

Pour limiter les risques d'attaque sur les sessions, il est recommandé d'activer deux options de configuration importantes dans PHP :

  • session.use_strict_mode : Empêche PHP d'accepter un identifiant de session qui n'a pas été généré par le serveur lui-même. Cela protège contre les attaques de fixation d'identifiant.
  • session.use_only_cookies : Interdit la transmission de l'identifiant de session dans l'URL, ce qui empêche qu'il soit exposé accidentellement (copier-coller, historique, liens externes, etc.).

Une attaque par fixation de session consiste à forcer un utilisateur à utiliser un identifiant de session défini à l'avance par un tiers malveillant. Si le serveur accepte cet identifiant sans le vérifier, l'attaquant pourra accéder à la session de la victime une fois qu'elle est authentifiée.


                    <?php
                    if (session_status() === PHP_SESSION_NONE)
                    {
                        // Sécuriser la session contre les attaques de fixation et les transmissions par URL.
                        ini_set('session.use_strict_mode', 1);
                        ini_set('session.use_only_cookies', 1);

                        session_start();
                    }
                    ?>
                

Dans certaines configurations anciennes ou incorrectes, PHP peut transmettre l'identifiant de session via l'URL (ex. : ?PHPSESSID=123456). Ce mode de fonctionnement est risqué, car il peut exposer involontairement l'identifiant (copier-coller, référents (en-tête HTTP Referer), liens partagés...).

Depuis PHP 5.3, cette pratique est désactivée par défaut, mais il est préférable de la bloquer explicitement avec session.use_only_cookies = 1.

Vérifier la configuration de session

Pour vous assurer que ces réglages sont bien appliqués, vous pouvez utiliser la fonction phpinfo() dans un fichier temporaire :


                    <?php
                    phpinfo();
                    ?>
                

Les paramètres de session s'affichent dans la section "Session" du tableau généré. Vérifiez que use_strict_mode et use_only_cookies sont bien activés (valeur On).

Le schéma ci-dessous résume les étapes internes du mécanisme de session PHP lors d'une requête HTTP, en tenant compte de la configuration des options précédentes :

Schéma du fonctionnement des sessions PHP

Bien que les variables de session soient immédiatement disponibles après un appel à session_start(), il est préférable de configurer explicitement le cookie que PHP utilise pour gérer l'identifiant de session.

La fonction session_set_cookie_params() permet de configurer le cookie que PHP utilise pour gérer l'identifiant de session. Elle accepte les mêmes options que setcookie() (comme secure, httponly, samesite, etc.), à une exception près, le paramètre lifetime ne représente pas une date d'expiration (comme expires), mais une durée de vie exprimée en secondes. Autrement dit, lifetime définit combien de temps le cookie sera conservé dans le navigateur à partir du moment où il est reçu.

Cette configuration doit être faite avant d'appeler session_start(). Si un cookie de session existe déjà dans la requête, les options fournies ne seront pas prises en compte.


                    session_set_cookie_params([
                        'lifetime' => 0,
                        'path' => "",           // Par défaut, le chemin de la page courante.
                        'domain' => "",         // Par défaut, le domaine de la page courante.
                        'secure' => false,
                        'httponly' => false,
                        'samesite' => ''        // Par défaut, comportement par défaut du navigateur.
                    ]): bool;
                

Il est recommandé de renforcer la sécurité du cookie de session à l'aide de trois options :

  • 'secure' => true : le cookie ne sera transmis que si la connexion utilise HTTPS. Cela limite le risque de détournement de session (session hijacking) en empêchant l'interception du cookie de session sur un réseau non sécurisé (comme un Wi-Fi public).
  • 'httponly' => true : le cookie devient inaccessible depuis JavaScript. Cela protège contre les attaques XSS (Cross-Site Scripting), où un code JavaScript injecté dans une page pourrait tenter de lire le cookie de session pour usurper l'identité de l'utilisateur.
  • 'samesite' => 'Lax' : le cookie n'est pas envoyé lors de certaines requêtes inter-sites. Cela empêche les attaques CSRF (Cross-Site Request Forgery), dans lesquelles un site malveillant tente de déclencher une action sur un autre site où l'utilisateur est déjà connecté (ex. : modifier un mot de passe à son insu).

                <?php
                if (session_status() === PHP_SESSION_NONE)
                {
                    ini_set('session.use_strict_mode', 1);
                    ini_set('session.use_only_cookies', 1);

                    // Ne configurer que si le cookie de session n'existe pas déjà.
                    // session_name() retourne le nom du cookie stockant l'identifiant de session (ex.: "PHPSESSID")
                    if (!isset($_COOKIE[session_name()]))
                    {
                        session_set_cookie_params([
                            'secure' => true,
                            'httponly' => true,
                            'samesite' => 'lax'
                        ]);
                    }

                    session_start();
                }
                ?>
                

Attention : si vous travaillez en HTTP (sans HTTPS), secure = true empêchera la création du cookie. Le navigateur ne pourra pas stocker l'identifiant, et PHP traitera chaque requête comme une nouvelle session.

Le paramètre lifetime permet de contrôler combien de temps le cookie PHPSESSID est conservé dans le navigateur. Il s'exprime en secondes :

  • 0 (valeur par défaut) : cookie supprimé à la fermeture du navigateur (cookie de session)
  • valeur non nulle : le cookie est conservé jusqu'à expiration (ex. : 7 jours)

                <?php
                if (session_status() === PHP_SESSION_NONE)
                {
                    ini_set('session.use_strict_mode', 1);
                    ini_set('session.use_only_cookies', 1);

                    if (!isset($_COOKIE[session_name()]))
                    {
                        // Calcul du temps de vie : 7 jours.
                        $dureeDeVie = 60 * 60 * 24 * 7;

                        session_set_cookie_params([
                            'lifetime' => $dureeDeVie,
                            'secure' => true,
                            'httponly' => true,
                            'samesite' => 'lax'
                        ]);
                    }

                    session_start();
                }
                ?>
                

Prolonger la durée de vie du cookie permet de maintenir la session active pendant plusieurs jours (fonctionnalité "Se souvenir de moi"), mais augmente les risques en cas de vol du cookie. Il est alors recommandé de mettre en place des vérifications supplémentaires côté serveur (adresse IP, type de navigateur, timestamp d'activité, etc.).

Notez que les systèmes "Se souvenir de moi" robustes utilisent plutôt un cookie personnalisé contenant un jeton sécurisé, associé à un identifiant en base de données, et non un simple cookie de session prolongé. Cela permet un meilleur contrôle, notamment pour révoquer une session à distance.

Destruction de la session

La fonction session_destroy() permet de mettre fin à une session. Elle supprime le fichier de session sur le serveur, dans lequel sont stockées les données liées à l'utilisateur.


                    session_destroy();
                

Attention : cet appel ne suffit pas à nettoyer totalement une session. Il ne vide pas le tableau $_SESSION pour la requête courante, et ne supprime pas le cookie PHPSESSID dans le navigateur.

Voici les étapes recommandées pour effacer proprement une session :


                    <?php
                    // 1. Démarrer la session si elle n'est pas active.
                    if (session_status() === PHP_SESSION_NONE) 
                    {
                        ini_set('session.use_strict_mode', 1);
                        ini_set('session.use_only_cookies', 1);

                        if (!isset($_COOKIE[session_name()]))
                        {
                            session_set_cookie_params([
                                'secure' => true,
                                'httponly' => true,
                                'samesite' => 'lax'
                            ]);
                        }

                        session_start();
                    }

                    // 2. Supprimer toutes les variables de session.
                    session_unset();

                    // 3. Supprimer le cookie contenant l'identifiant (côté client).
                    //
                    // Tester la valeur de ini_get("session.use_cookies") : 
                    //     TRUE : Indique que PHP utilise un cookie pour stocker l'identifiant de session.
                    //     FALSE : Identifiant de session transmis dans l'URL.
                    if (ini_get("session.use_cookies"))
                    {
                        // Récupérer la configuration du cookie de session.
                        $params = session_get_cookie_params();

                        setcookie(
                            session_name(),     // Exemple : "PHPSESSID"
                            '',                 // Valeur vide
                            [
                                'expires' => time() - 3600,
                                'path' => $params['path'],
                                'domain' => $params['domain']
                            ]
                        );
                    }

                    // 4. Détruire la session côté serveur.
                    session_destroy();
                    ?>
                

Ce processus garantit que :

  • Les variables de session sont effacées ($_SESSION)
  • Le cookie contenant l'identifiant est supprimé du navigateur
  • Le fichier de session est supprimé côté serveur

Renouveler l'identifiant de session

Pour limiter les risques d'usurpation, il est recommandé de changer l'identifiant de session chaque fois que le niveau de privilège de l'utilisateur change. Par exemple, lors d'une connexion, d'une déconnexion ou d'un passage en mode administrateur.

En PHP, cela se fait avec la fonction session_regenerate_id(), qui génère un nouvel identifiant tout en conservant les données stockées dans $_SESSION.


                    if (session_status() === PHP_SESSION_NONE) 
                    {
                        session_start();
                    }

                    // Régénérer l'identifiant et supprimer le fichier de l'ancienne session.
                    if (!session_regenerate_id(true)) 
                    {
                        // Gérer une éventuelle erreur (journalisation, message utilisateur, etc.)
                    }
                

Le paramètre true demande à PHP de supprimer immédiatement le fichier de session associé à l'ancien identifiant, ce qui réduit la surface d'attaque.

Sans ce paramètre, l'ancien fichier reste sur le serveur jusqu'à ce que PHP le nettoie automatiquement. Ce nettoyage dépend des réglages session.gc_maxlifetime, session.gc_probability et session.gc_divisor. Il peut se produire bien plus tard que prévu et, pendant ce temps, l'ancien identifiant reste utilisable. Cela crée une fenêtre de vulnérabilité possible.

La régénération est une défense très efficace contre les attaques par fixation de session, où un pirate tente de forcer un identifiant connu chez sa victime. En générant un nouvel identifiant au moment de la connexion, on s'assure que l'identifiant que le pirate connaissait ne donne plus accès à rien.

Elle n'empêche en revanche pas le vol d'identifiant en cours de session, par exemple via une faille XSS ou une écoute du réseau. Pour ce type d'attaque, les protections principales sont l'utilisation systématique de HTTPS et la configuration correcte du cookie de session avec les attributs Secure, HttpOnly et SameSite.

Régénérer périodiquement l'identifiant pendant la session peut compléter ces protections en réduisant la durée pendant laquelle un identifiant volé reste utilisable.

Le problème des requêtes parallèles

Quand un script PHP appelle session_regenerate_id(), PHP ajoute automatiquement un en-tête Set-Cookie dans la réponse HTTP pour transmettre le nouvel identifiant au navigateur. Tant que cette réponse est bien reçue, le navigateur met à jour son cookie et utilise le nouvel identifiant dans toutes ses requêtes suivantes.

Le souci est qu'au moment précis de la régénération, plusieurs requêtes peuvent déjà être en vol. Par exemple, une page qui se charge en déclenchant en parallèle des appels AJAX, des chargements d'images ou des requêtes vers une API interne. Ces requêtes ont quitté le navigateur avant que le nouveau cookie n'arrive, donc elles contiennent encore l'ancien identifiant. Si le fichier de l'ancienne session a déjà été supprimé, ces requêtes échouent et l'utilisateur voit une partie de sa page tomber.

Plus rarement, sur des réseaux peu fiables (Wi-Fi public, mobile...), la réponse contenant le nouveau cookie peut être perdue ou retardée. Le navigateur continue alors d'envoyer l'ancien identifiant pendant un certain temps.

Dans ces cas, si vous avez utilisé session_regenerate_id(true), l'ancien identifiant a déjà été supprimé côté serveur. Cela peut entraîner une perte de session apparente, voire provoquer une déconnexion inattendue.

Utiliser une session de transition

Pour éviter ces problèmes, il est possible de retarder la suppression de l'ancienne session. L'idée est de laisser l'ancien identifiant rester valide pendant quelques secondes, le temps que les requêtes parallèles déjà en cours se terminent et que le nouveau cookie soit bien pris en compte par le navigateur.

Avant de lire le code, il faut se rappeler qu'une session PHP repose sur deux éléments. D'un côté, un cookie envoyé au navigateur qui contient l'identifiant. De l'autre, un fichier sur le serveur qui contient les données associées (le contenu de $_SESSION). Lors d'une régénération, on crée un nouvel identifiant et un nouveau fichier. La question est de savoir quoi faire de l'ancien fichier.

La méthode de transition consiste à procéder en plusieurs étapes.

  1. Copier le contenu de $_SESSION dans une variable temporaire (par exemple $ancienneSession), afin de conserver les données pendant le changement d'identifiant.
  2. Ajouter une clé spéciale dans $_SESSION (comme $_SESSION['obsolete']) pour signaler que la session associée à l'identifiant actuel est en fin de vie. On lui associe un délai d'expiration (par exemple time() + 120) pendant lequel l'ancien identifiant restera encore accepté. Cette clé sera enregistrée dans le fichier de l'ancien identifiant.
  3. Appeler session_regenerate_id(false) pour générer un nouvel identifiant sans supprimer le fichier de l'ancien. À partir de ce moment, $_SESSION est associé au nouvel identifiant, mais l'ancien fichier reste disponible sur le serveur avec le marqueur d'obsolescence.
  4. Restaurer les données sauvegardées depuis $ancienneSession dans $_SESSION. Comme la sauvegarde a été faite avant l'ajout de la clé obsolete, cette clé n'est pas recopiée dans la nouvelle session. Le marqueur reste donc présent uniquement dans le fichier de l'ancien identifiant.
  5. À chaque chargement de page, vérifier si la session contient une clé obsolete et si le délai est dépassé. Cette vérification ne déclenchera quelque chose que pour les requêtes qui utilisent encore l'ancien identifiant, puisque la nouvelle session ne contient pas ce marqueur.
  6. Lorsque le délai est écoulé, effacer les données de session avec session_unset(), supprimer le cookie PHPSESSID avec les bons paramètres, puis appeler session_destroy() pour supprimer le fichier côté serveur.

                        <?php
                        // Script à déclencher uniquement lors d'un changement de statut (par exemple lors d'une connexion réussie).

                        if (session_status() === PHP_SESSION_NONE) 
                        {
                            session_start();
                        }

                        // Sauvegarder les données actuelles dans une variable temporaire.
                        $ancienneSession = $_SESSION;

                        // Marquer la session associée à l'ancien identifiant comme obsolète pour 120 secondes.
                        $_SESSION['obsolete'] = time() + 120;

                        // Créer un nouvel identifiant de session sans supprimer le fichier de l'ancien.
                        if (!session_regenerate_id(false)) 
                        {
                            // Gérer l'erreur...
                        }

                        // À ce stade, $_SESSION est associé au NOUVEL identifiant.
                        // On y restaure les données d'origine, sans la clé 'obsolete'.
                        // Cette clé reste enregistrée dans le fichier de l'ancien identifiant.
                        $_SESSION = $ancienneSession;
                        ?>
                    

Ensuite, dans chaque script où la session est utilisée, on peut vérifier si l'identifiant en cours est obsolète. Si le délai est écoulé, on supprime proprement la session et son cookie.


                        <?php
                        if (session_status() === PHP_SESSION_NONE) 
                        {
                            session_start();
                        }

                        // Vérifier si l'identifiant actuel est obsolète et si le délai est écoulé.
                        if (isset($_SESSION['obsolete']) && time() >= $_SESSION['obsolete']) 
                        {
                            // Vider le contenu de $_SESSION.
                            session_unset();

                            // Supprimer le cookie côté navigateur.
                            // On réutilise les mêmes paramètres que ceux utilisés à la création,
                            // sinon certains navigateurs traitent le nouveau cookie comme différent et ne remplacent pas l'ancien.
                            if (ini_get("session.use_cookies")) 
                            {
                                $params = session_get_cookie_params();
                                setcookie(session_name(), '', [
                                    'expires'  => time() - 3600,
                                    'path'     => $params['path'],
                                    'domain'   => $params['domain'],
                                    'secure'   => $params['secure'],
                                    'httponly' => $params['httponly'],
                                    'samesite' => $params['samesite'] ?? 'Lax'
                                ]);
                            }

                            // Supprimer le fichier de session côté serveur.
                            session_destroy();
                        }
                        ?>
                    

Portée et limite de cette approche

Cette technique règle bien le cas des requêtes parallèles. Au moment de la régénération, toutes les requêtes déjà en vol qui contiennent l'ancien identifiant aboutissent normalement pendant la fenêtre de transition. Une fois ces requêtes terminées, le navigateur n'utilise plus que le nouvel identifiant et la vie continue.

Elle ne règle pas le cas où la réponse contenant le nouveau cookie est totalement perdue. Si le navigateur n'apprend jamais l'existence du nouvel identifiant, il continuera d'envoyer l'ancien jusqu'à l'expiration de la fenêtre, puis sera déconnecté. C'est un coût considéré comme acceptable, parce qu'un utilisateur dans cette situation a une connexion suffisamment dégradée pour que la session ait peu de chances de fonctionner correctement de toute façon. Le forcer à se reconnecter est même plus sûr.

Une variante plus sophistiquée existerait pour couvrir aussi ce cas. Elle consiste à stocker le nouvel identifiant à l'intérieur du fichier obsolète, et à renvoyer un Set-Cookie au navigateur lorsqu'une requête arrive avec l'ancien identifiant pendant la fenêtre de transition. Cette variante ramène le navigateur sur le bon identifiant sans le déconnecter.

Le problème est qu'elle annule en grande partie le bénéfice de sécurité de la régénération. Si un pirate a volé l'ancien cookie et effectue une requête pendant la fenêtre de transition, le serveur ne peut pas distinguer le pirate de l'utilisateur légitime, puisque tous deux présentent exactement le même cookie. Tous deux reçoivent automatiquement le nouvel identifiant. La régénération perd alors une partie de son sens, puisque l'identifiant compromis a tranquillement été remplacé par un identifiant tout aussi compromis.

Pour cette raison, la pratique courante reste la version simple présentée ici. La régénération protège contre la fixation, la fenêtre de transition absorbe les requêtes parallèles, et les autres mécanismes (HTTPS, attributs du cookie, expiration de session, réauthentification sur les actions sensibles) prennent en charge le reste.

Exercice : Projet Progressif 05

  • Objectif : Mettre en place un système de gestion d'authentification utilisant les variables de session pour identifier un utilisateur connecté, maintenir sa connexion d'une page à l'autre et permettre sa déconnexion.
  • Instructions :
    1. Poursuivez le projet progressif initié lors du chapitre sur les modèles de pages dynamiques.
    2. Créez un gestionnaire d'authentification, par exemple dans un fichier distinct (ex.: gestionAuthentification.php). Ce gestionnaire devra contenir trois fonctions principales :
      • connecter_utilisateur() : Enregistrer l'ID de l'utilisateur dans une variable de session (ex.: $_SESSION['utilisateurId']), ce qui permettra de savoir qu'un compte est connecté.
      • est_connecte() : Vérifier, en consultant la variable de session, si l'utilisateur est effectivement connecté.
      • deconnecter_utilisateur() : Supprimer la variable de session liée à l'utilisateur connecté.
    3. Ajoutez une page de profil contenant :
      • Un titre : profil.
      • Récupérez l'ID de l'utilisateur stocké en session et interrogez la base de données pour afficher son pseudo et son email.
      • Un formulaire de déconnexion ne comportant qu'un bouton Déconnexion. Lorsqu'il est cliqué, il doit lancer la fonction deconnecter_utilisateur().
    4. Dans votre page de connexion, appelez la fonction connecter_utilisateur() après avoir validé les informations du formulaire et identifié correctement l'utilisateur.
    5. Utilisez la fonction est_connecte() pour gérer les redirections :
      • Si l'utilisateur essaie d'accéder à la page connexion ou inscription alors qu'il est déjà connecté, redirigez-le vers la page de profil.
      • Si l'utilisateur tente d'accéder à la page profil alors qu'il n'est pas connecté, redirigez-le vers la page de connexion.

Bonus (facultatif)

  • Objectif : Ajouter une étape de vérification par email lors de la connexion pour renforcer la sécurité des comptes utilisateurs.
  • Instructions :
    1. Modifiez le processus de connexion pour inclure une vérification par code.
    2. Lorsque l'utilisateur saisit son pseudo/email et mot de passe :
      • Vérifiez que les informations sont correctes.
      • Générez un code de 6 chiffres aléatoires.
      • Stockez temporairement ce code dans la base de données ou dans une variable de session.
      • Envoyez le code par email à l'utilisateur.
      • Redirigez l'utilisateur vers une page de vérification.
    3. Créez une page de vérification du code :
      • Affichez un formulaire où l'utilisateur doit saisir le code reçu.
      • Ajoutez un bouton pour demander un nouvel envoi du code.
      • Si l'utilisateur demande un nouveau code.
      • Si le code saisi est correct :
        • Connectez l'utilisateur.
        • Stockez son état de connexion en session.
        • Redirigez-le vers la page de son choix (ex. : profil).
        • Supprimez le code pour qu'il ne soit plus utilisable.
      • Si le code est incorrect :
        • Affichez un message d'erreur.
        • Laissez l'utilisateur réessayer.