$options['lifetime'] ?? 0, // Session cookie (expire à la fermeture du navigateur) 'path' => $options['path'] ?? '/', 'domain' => $options['domain'] ?? '', 'secure' => $options['secure'] ?? self::isHttps(), // HTTPS uniquement si disponible 'httponly' => true, // Empêche l'accès JavaScript au cookie de session 'samesite' => $options['samesite'] ?? 'Lax' // Protection CSRF (Lax ou Strict) ]; // Applique les paramètres des cookies de session session_set_cookie_params($cookieParams); // Configuration supplémentaire de sécurité ini_set('session.use_strict_mode', '1'); // Refuse les IDs de session non initialisés ini_set('session.use_only_cookies', '1'); // Utilise uniquement les cookies, pas l'URL ini_set('session.cookie_httponly', '1'); // Protection XSS ini_set('session.cookie_samesite', $cookieParams['samesite']); // Protection CSRF // Si HTTPS est disponible, force les cookies sécurisés if ($cookieParams['secure']) { ini_set('session.cookie_secure', '1'); } // Utilise un algorithme de hachage fort pour les IDs de session ini_set('session.hash_function', 'sha256'); ini_set('session.hash_bits_per_character', '5'); // Régénération de l'ID de session pour prévenir la fixation de session if (isset($options['session_name'])) { session_name($options['session_name']); } // Démarre la session $started = session_start(); // Régénère l'ID de session périodiquement pour plus de sécurité if ($started && !isset($_SESSION['_session_created'])) { $_SESSION['_session_created'] = time(); $_SESSION['_session_ip'] = self::getClientIP(); $_SESSION['_session_user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? ''; } // Régénère l'ID toutes les 30 minutes if ($started && isset($_SESSION['_session_created'])) { $sessionAge = time() - $_SESSION['_session_created']; if ($sessionAge > 1800) { // 30 minutes self::regenerateId(); $_SESSION['_session_created'] = time(); } } // Validation de l'intégrité de la session if ($started && !self::validateSession()) { self::destroy(); return self::start($options); // Redémarre une nouvelle session propre } return $started; } /** * Régénère l'ID de session de manière sécurisée. * * @return bool True si la régénération a réussi */ public static function regenerateId(): bool { if (session_status() !== PHP_SESSION_ACTIVE) { return false; } return session_regenerate_id(true); // true = supprime l'ancienne session } /** * Détruit complètement une session de manière sécurisée. * * @return bool True si la session a été détruite avec succès */ public static function destroy(): bool { if (session_status() !== PHP_SESSION_ACTIVE) { return true; } // Efface toutes les variables de session $_SESSION = []; // Détruit le cookie de session if (ini_get('session.use_cookies')) { $params = session_get_cookie_params(); setcookie( session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly'] ); } // Détruit la session return session_destroy(); } /** * Valide l'intégrité de la session en vérifiant l'IP et le User-Agent. * * @return bool True si la session est valide */ private static function validateSession(): bool { // Vérification de l'IP (optionnelle, peut causer des problèmes avec certains FAI) if (isset($_SESSION['_session_ip'])) { $currentIP = self::getClientIP(); if ($_SESSION['_session_ip'] !== $currentIP) { // Option stricte : retourner false // Pour l'instant, on log seulement error_log("Session IP mismatch: {$_SESSION['_session_ip']} vs $currentIP"); } } // Vérification du User-Agent if (isset($_SESSION['_session_user_agent'])) { $currentUA = $_SERVER['HTTP_USER_AGENT'] ?? ''; if ($_SESSION['_session_user_agent'] !== $currentUA) { error_log("Session User-Agent mismatch"); return false; // Plus strict pour le User-Agent } } return true; } /** * Détecte si la connexion est en HTTPS. * * @return bool True si HTTPS est activé */ private static function isHttps(): bool { if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') { return true; } if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { return true; } if (!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] === 'on') { return true; } return (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443); } /** * Récupère l'IP réelle du client (gère les proxies). * * @return string L'adresse IP du client */ private static function getClientIP(): string { $headers = [ 'HTTP_CF_CONNECTING_IP', // Cloudflare 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'HTTP_CLIENT_IP', 'REMOTE_ADDR' ]; foreach ($headers as $header) { if (!empty($_SERVER[$header])) { $ip = $_SERVER[$header]; // Si plusieurs IPs (proxy chain), prendre la première if (strpos($ip, ',') !== false) { $ips = explode(',', $ip); $ip = trim($ips[0]); } // Valider que c'est une IP valide if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { return $ip; } } } return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; } /** * Configure le timeout d'inactivité de session. * * @param int $minutes Nombre de minutes d'inactivité avant expiration * @return bool True si le timeout est configuré */ public static function setInactivityTimeout(int $minutes): bool { if (session_status() !== PHP_SESSION_ACTIVE) { return false; } $timeout = $minutes * 60; if (isset($_SESSION['_session_last_activity'])) { $elapsed = time() - $_SESSION['_session_last_activity']; if ($elapsed > $timeout) { self::destroy(); return false; } } $_SESSION['_session_last_activity'] = time(); return true; } /** * Récupère les informations de sécurité de la session actuelle. * * @return array Informations de sécurité */ public static function getSecurityInfo(): array { if (session_status() !== PHP_SESSION_ACTIVE) { return []; } return [ 'session_id' => session_id(), 'session_name' => session_name(), 'created' => $_SESSION['_session_created'] ?? null, 'last_activity' => $_SESSION['_session_last_activity'] ?? null, 'ip' => $_SESSION['_session_ip'] ?? null, 'is_https' => self::isHttps(), 'cookie_params' => session_get_cookie_params() ]; } }