Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 620 minIntermédiaire

Sessions et cookies en PHP

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • expliquer comment une session relie le navigateur au serveur ;
  • stocker et lire des données dans $_SESSION ;
  • sécuriser le cookie de session et gérer l'expiration ;
  • choisir entre une session et un cookie.

4 exercices corrigés1 schéma

Une session PHP garde des informations sur un visiteur d'une page à l'autre : les données restent sur le serveur, dans le tableau $_SESSION, et le navigateur ne conserve qu'un identifiant aléatoire dans un cookie. C'est ce mécanisme qui permet de rester connecté, de garder une sélection en cours ou d'afficher un message après une redirection. Ce chapitre explique comment fonctionnent les sessions, comment les configurer de façon sûre, et quand utiliser un cookie à la place.

Pourquoi il faut une session

HTTP est un protocole sans état : le serveur traite chaque requête indépendamment des autres, et ton script PHP repart de zéro à chaque fois (chapitre 1). Quand Julie Lefèvre se connecte à Prêtmat puis clique sur « Mes emprunts », rien dans la deuxième requête ne dit au serveur que c'est elle. Il faut donc que chaque requête transporte une information qui permet de la reconnaître.

Deux solutions existent :

  • La session côté serveur : le serveur stocke les informations (qui est connecté, avec quel rôle) et donne au navigateur un simple numéro de dossier, l'identifiant de session, dans un cookie. C'est le fonctionnement par défaut de PHP.
  • Le jeton (par exemple un JWT) : le serveur signe les informations et les confie au client, qui les renvoie à chaque requête. C'est le modèle habituel des API (cours API REST).

Pour une application web en PHP, la session est la solution simple et sûre.

Comment fonctionne une session PHP

NavigateurServeur PHP1re requête : la connexionPOST /connexionsession_start()crée l'identifiantSet-Cookie: PHPSESSID=7f3a…cookie gardéPHPSESSID=7f3a…fichier sess_7f3a…utilisateur =>id: 2nom: Julie Lefèvrerole: employejamais envoyé au clientrequêtes suivantesGET /mes-empruntsCookie: PHPSESSID=7f3a…session_start()relit le fichier etremplit $_SESSIONle navigateur jointle cookie à chaquerequête vers le site
Le navigateur ne garde que l'identifiant ; les données de session restent sur le serveur.
  1. Au premier appel de session_start(), PHP génère un identifiant aléatoire (32 caractères par défaut) et crée un espace de stockage vide pour cet identifiant : par défaut, un fichier sess_<identifiant> dans un dossier temporaire du serveur.
  2. La réponse contient un en-tête Set-Cookie: PHPSESSID=<identifiant>; path=/. Le navigateur enregistre ce cookie.
  3. À chaque requête suivante vers le même site, le navigateur renvoie automatiquement le cookie. session_start() lit l'identifiant, retrouve le fichier et remplit $_SESSION avec son contenu.
  4. À la fin du script, PHP réécrit le fichier avec le contenu de $_SESSION.

Le visiteur ne voit et ne peut modifier que l'identifiant. Le rôle « gestionnaire » enregistré dans $_SESSION reste sur le serveur : c'est ce qui rend la session fiable, à condition que l'identifiant ne puisse être ni deviné, ni volé.

session_start et $_SESSION

php
<?php

declare(strict_types=1);

session_start();

// Écrire
$_SESSION['utilisateur'] = ['id' => 2, 'nom' => 'Julie Lefèvre', 'role' => 'employe'];
$_SESSION['selection'] ??= [];          // une liste de matériels choisis
$_SESSION['selection'][] = 4;

// Lire
$nom = $_SESSION['utilisateur']['nom'] ?? null;   // null si l'utilisateur n'est pas connecté

// Supprimer une clé
unset($_SESSION['selection']);

Ce qu'il faut savoir :

  • session_start() envoie un en-tête (le cookie) : il doit être appelé avant tout affichage, comme header(). Sinon : « Session cannot be started after headers have already been sent ». Dans une application organisée, il est appelé une seule fois, au début du contrôleur frontal (chapitre 12).
  • $_SESSION est un tableau ordinaire : tu y ranges des chaînes, des nombres, des tableaux. Évite d'y ranger des objets complexes, et jamais un objet PDO : il ne peut pas être enregistré.
  • Stocke peu : un identifiant d'utilisateur plutôt que toute la ligne de la base. Une session est relue et réécrite à chaque requête.
  • Une session n'est pas une base de données : elle disparaît à la déconnexion, à l'expiration ou au nettoyage du serveur. Ce qui doit durer va en base.

Les réglages par défaut de PHP sont faits pour la compatibilité, pas pour la sécurité : le cookie de session n'a ni HttpOnly, ni Secure, ni SameSite. Règle-les dans php.ini ou, plus simplement, au moment de session_start() :

php
<?php

declare(strict_types=1);

session_start([
    'name' => 'pretmat_session',      // un nom propre à l'application
    'cookie_httponly' => true,        // invisible pour JavaScript
    'cookie_secure' => true,          // envoyé seulement en HTTPS
    'cookie_samesite' => 'Lax',       // pas envoyé par les formulaires des autres sites
    'use_strict_mode' => true,        // refuse un identifiant que le serveur n'a pas créé
    'gc_maxlifetime' => 1800,         // données supprimables après 30 min sans activité
]);
RéglageDéfaut de PHPRecommandéCe qu'il protège
cookie_httponlydésactivétrueUn script injecté par une XSS ne peut pas lire le cookie
cookie_securedésactivétrue en productionLe cookie ne circule jamais en clair sur le réseau
cookie_samesitevideLax (ou Strict)Limite les requêtes forgées depuis un autre site (CSRF)
use_strict_modedésactivétrueUn identifiant inventé par un attaquant est refusé et remplacé
use_only_cookiesactivéactivéL'identifiant ne passe jamais dans l'URL
cookie_lifetime00Le cookie disparaît à la fermeture du navigateur

Les attributs d'un cookie et leur rôle face à la CSRF sont détaillés dans le cours Sécurité des applications.

Les messages flash

Un message flash est un message stocké en session pour être affiché une seule fois, à la page suivante : « Matériel ajouté », « Mot de passe incorrect ». C'est le compagnon naturel du motif Post/Redirect/Get.

php
<?php

declare(strict_types=1);

// src/helpers.php
function flash(string $type, string $message): void
{
    $_SESSION['flash'][] = ['type' => $type, 'message' => $message];
}

/** Renvoie les messages en attente et les retire de la session. */
function flashes(): array
{
    $messages = $_SESSION['flash'] ?? [];
    unset($_SESSION['flash']);
    return $messages;
}
php
<?php
// Dans le traitement, après l'enregistrement
flash('succes', 'Le matériel PC-0103 a été ajouté.');
header('Location: /materiels.php', true, 303);
exit;
php
<!-- Dans le gabarit commun, en haut de chaque page -->
<?php foreach (flashes() as $f): ?>
  <p class="alerte alerte-<?= e($f['type']) ?>" role="status"><?= e($f['message']) ?></p>
<?php endforeach; ?>

Expiration et déconnexion

Une session doit avoir une fin. Pour une application de gestion, une expiration après 30 minutes d'inactivité est un bon réglage. Le paramètre gc_maxlifetime dit seulement à partir de quand le serveur peut supprimer les fichiers de session, au gré d'un nettoyage aléatoire : pour une expiration fiable, contrôle toi-même la date de dernière activité.

php
<?php

declare(strict_types=1);

const INACTIVITE_MAX = 1800;   // 30 minutes, en secondes

if (isset($_SESSION['derniere_activite']) && time() - $_SESSION['derniere_activite'] > INACTIVITE_MAX) {
    deconnecter();
    flash('info', 'Ta session a expiré, reconnecte-toi.');
    header('Location: /connexion.php', true, 303);
    exit;
}
$_SESSION['derniere_activite'] = time();

La déconnexion doit détruire la session côté serveur, pas seulement oublier le cookie :

php
<?php

declare(strict_types=1);

function deconnecter(): void
{
    $_SESSION = [];                                   // vide les données
    $p = session_get_cookie_params();
    setcookie(session_name(), '', [                   // supprime le cookie du navigateur
        'expires' => time() - 3600,
        'path' => $p['path'],
        'domain' => $p['domain'],
        'secure' => $p['secure'],
        'httponly' => $p['httponly'],
        'samesite' => $p['samesite'],
    ]);
    session_destroy();                                // supprime le fichier de session
    session_start();                                  // nouvelle session vide, pour les messages flash
    session_regenerate_id(true);
}

La déconnexion se déclenche par un formulaire POST (un bouton « Se déconnecter »), pas par un lien en GET : sinon, n'importe quelle page pourrait déconnecter tes utilisateurs en chargeant cette adresse.

Les attaques contre la session

L'identifiant de session vaut un mot de passe : celui qui le possède est l'utilisateur, sans rien saisir. Trois façons de l'obtenir, et leurs parades :

AttaquePrincipeParade
Vol de sessionL'attaquant lit le cookie : par un script injecté (XSS), sur un réseau Wi-Fi sans HTTPS, ou sur un poste partagéHttpOnly, HTTPS avec Secure, expiration, bouton de déconnexion
Fixation de sessionL'attaquant impose à la victime un identifiant qu'il connaît, avant qu'elle se connecte ; une fois la victime connectée, cet identifiant ouvre son comptesession_regenerate_id(true) juste après la connexion, use_strict_mode
Requête forgée (CSRF)Une page d'un autre site déclenche une action dans Prêtmat ; le navigateur joint le cookie de session automatiquementSameSite=Lax et un jeton anti-CSRF dans chaque formulaire (chapitre 9)

Les cookies

Un cookie est une petite donnée (4 Ko au plus) que le serveur demande au navigateur de garder et de renvoyer à chaque requête vers le site. Contrairement à la session, la donnée est chez le client : il peut la lire et la modifier.

php
<?php

declare(strict_types=1);

// Écrire : avant tout affichage, comme header()
setcookie('affichage', 'grille', [
    'expires' => time() + 60 * 60 * 24 * 180,   // 180 jours
    'path' => '/',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);

// Lire : à la requête SUIVANTE, le cookie revient dans $_COOKIE
$affichage = $_COOKIE['affichage'] ?? 'liste';
$affichage = in_array($affichage, ['liste', 'grille'], true) ? $affichage : 'liste';

Le cookie posé par setcookie() n'apparaît dans $_COOKIE qu'à la requête suivante, quand le navigateur le renvoie. Et comme n'importe quelle donnée reçue, sa valeur se valide : ici, une liste blanche de deux valeurs.

SessionCookie
Où sont les donnéesSur le serveurDans le navigateur
Le client peut-il les modifier ?Non (il n'a que l'identifiant)Oui, librement
TailleLimitée par le serveur4 Ko par cookie
DuréeJusqu'à la déconnexion ou l'expirationJusqu'à la date expires, même après fermeture du navigateur
UsageUtilisateur connecté, rôle, sélection en cours, jeton CSRF, messages flashPréférences d'affichage, langue, consentement aux cookies

Côté réglementation, les cookies strictement nécessaires au service (le cookie de session, le panier, le choix de langue explicite) ne demandent pas de consentement. Les cookies de mesure d'audience non exemptés, de publicité ou de réseaux sociaux en demandent un, selon les recommandations de la CNIL. Le cours Droit du numérique et RGPD détaille ces règles.

Exercices

Exercice 1 sur 4 La sélection de matériel Réussi À revoir

Dans Prêtmat, un employé peut sélectionner plusieurs matériels avant de valider une demande d'emprunt groupée. Écris, avec la session :

  1. une fonction ajouterALaSelection(int $idMateriel): void qui ajoute un identifiant sans créer de doublon ;
  2. une fonction retirerDeLaSelection(int $idMateriel): void ;
  3. une fonction nbSelectionnes(): int.

Pourquoi stocker les identifiants plutôt que les lignes complètes des matériels ?

Voir la réponseMasquer la réponse de l'exercice 1
php
<?php
declare(strict_types=1);

function ajouterALaSelection(int $idMateriel): void
{
    $_SESSION['selection'] ??= [];
    if (!in_array($idMateriel, $_SESSION['selection'], true)) {
        $_SESSION['selection'][] = $idMateriel;
    }
}

function retirerDeLaSelection(int $idMateriel): void
{
    $_SESSION['selection'] = array_values(array_filter(
        $_SESSION['selection'] ?? [],
        fn(int $id): bool => $id !== $idMateriel
    ));
}

function nbSelectionnes(): int
{
    return count($_SESSION['selection'] ?? []);
}

Les identifiants suffisent : au moment d'afficher ou de valider, on relit les matériels en base. On a ainsi toujours leur état à jour (un matériel peut avoir été prêté à quelqu'un d'autre entre-temps), et la session reste légère. Les fonctions qui reçoivent l'identifiant attendent un int : le contrôleur doit valider le paramètre reçu avant de les appeler.

Tu avais trouvé ?

Exercice 2 sur 4 Un cookie de trop Réussi À revoir

Un étudiant a écrit cette page, appelée après la vérification du mot de passe. Explique comment un employé peut devenir gestionnaire, puis corrige.

php
<?php
setcookie('id_utilisateur', (string) $utilisateur['id']);
setcookie('role', $utilisateur['role']);
header('Location: /accueil.php');
exit;
php
<?php
// accueil.php
if (($_COOKIE['role'] ?? '') === 'gestionnaire') {
    afficherMenuGestion();
}
Voir la réponseMasquer la réponse de l'exercice 2

Les cookies sont stockés et modifiables par le navigateur. Il suffit à l'employé d'ouvrir les outils de développement, onglet Application (ou Stockage), et de remplacer la valeur du cookie role par gestionnaire ; il peut aussi changer id_utilisateur pour se faire passer pour un autre. C'est un contrôle d'accès défaillant : l'application fait confiance à une donnée fournie par le client.

Correction : les informations de connexion vont en session, et l'identifiant est régénéré.

php
<?php
session_regenerate_id(true);
$_SESSION['utilisateur'] = ['id' => $utilisateur['id'], 'nom' => $utilisateur['nom'], 'role' => $utilisateur['role']];
header('Location: /accueil.php', true, 303);
exit;
php
<?php
// accueil.php (session_start() appelé au début de chaque page)
if (($_SESSION['utilisateur']['role'] ?? '') === 'gestionnaire') {
    afficherMenuGestion();
}

Et cacher le menu ne suffit pas : chaque page et chaque action de gestion doit elle-même vérifier le rôle en session (chapitre 9).

Tu avais trouvé ?

Exercice 3 sur 4 Headers already sent Réussi À revoir

Cette page affiche « Warning: session_start(): Session cannot be started after headers have already been sent » et l'utilisateur n'est jamais reconnu. Le fichier commence par une ligne vide, puis :

php
<?php
require 'menu.php';     // affiche le menu HTML
session_start();

Trouve les deux causes et corrige.

Voir la réponseMasquer la réponse de l'exercice 3
  1. La ligne vide avant <?php est du contenu envoyé au navigateur : dès qu'un octet de corps part, les en-têtes sont figés. Il faut que <?php soit tout au début du fichier (attention aussi au BOM UTF-8 que certains éditeurs ajoutent : enregistre en « UTF-8 sans BOM »).
  2. menu.php affiche du HTML avant session_start(). Il faut démarrer la session avant tout affichage :
php
<?php
session_start();
require 'menu.php';

Dans une application organisée, session_start() est appelé une seule fois, en tête du contrôleur frontal, et les vues ne sont incluses qu'à la fin : le problème ne peut plus se produire (chapitre 12).

Tu avais trouvé ?

Exercice 4 sur 4 Combien de temps reste-t-il ? Réussi À revoir

INACTIVITE_MAX vaut 1800. Julie a cliqué pour la dernière fois à 10 h 12 min 30 s. Elle clique à nouveau à 10 h 41 min 10 s, puis à 11 h 12 min 00 s. Que se passe-t-il à chaque clic ?

Voir la réponseMasquer la réponse de l'exercice 4
  • À 10 h 41 min 10 s, il s'est écoulé 28 min 40 s, soit 1 720 secondes : moins de 1 800, la session continue et derniere_activite passe à 10 h 41 min 10 s.
  • À 11 h 12 min 00 s, il s'est écoulé 30 min 50 s depuis le dernier clic, soit 1 850 secondes : plus de 1 800, Julie est déconnectée et redirigée vers la page de connexion avec le message « Ta session a expiré ».

L'inactivité se compte depuis la dernière requête, pas depuis la connexion : c'est pour cela que le code met à jour derniere_activite à chaque page.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : PDO : connexion à MySQL et requêtes préparéesRevoir le sommaire du cours