PHP Sessions and Cookies
Introduction to Sessions and Cookies
HTTP is a stateless protocol, meaning each request is independent and the server doesn't retain information about previous requests. Sessions and cookies provide mechanisms to maintain state and user data across multiple HTTP requests, enabling features like user authentication, shopping carts, and personalized experiences.
Understanding sessions and cookies is fundamental to web development, as they form the backbone of user experience and application functionality in modern web applications.
Why State Management Matters
User Experience: Users expect websites to remember their preferences, login status, and shopping cart contents as they navigate between pages.
Authentication: Sessions enable secure user authentication by maintaining login state without requiring users to authenticate on every page request.
Personalization: Persistent data allows applications to provide personalized content, recommendations, and user-specific settings.
Application Functionality: Many web application features depend on maintaining state across requests, from multi-step forms to complex user workflows.
Sessions vs. Cookies: Understanding the Difference
Cookies are small pieces of data stored in the user's browser and sent with every request to the server. They persist between browser sessions and can be set to expire at specific times.
Sessions are server-side storage mechanisms that use a session identifier (typically stored in a cookie) to associate server-side data with a specific user session.
Key Differences:
- Storage Location: Cookies are stored client-side, sessions server-side
- Security: Sessions are more secure as data never leaves the server
- Capacity: Cookies have strict size limits (~4KB), sessions can store larger amounts of data
- Performance: Cookies are sent with every request, sessions only send the session ID
Cookies in PHP
Basic Cookie Operations
<?php
/**
* PHP Cookie Management
*
* Cookies provide client-side data persistence and are essential
* for maintaining user preferences and simple state information.
*/
/**
* Setting cookies with various options
*/
function demonstrateCookieSettings(): void
{
// Basic cookie (expires when browser closes)
setcookie('username', 'john_doe');
// Cookie with expiration time (1 week from now)
$expiry = time() + (7 * 24 * 60 * 60); // 7 days
setcookie('user_preference', 'dark_theme', $expiry);
// Secure cookie with all options
setcookie('session_token', 'abc123def456', [
'expires' => time() + 3600, // 1 hour
'path' => '/', // Available site-wide
'domain' => '.example.com', // Available to subdomains
'secure' => true, // Only over HTTPS
'httponly' => true, // Not accessible via JavaScript
'samesite' => 'Strict' // CSRF protection
]);
// Cookie for specific path
setcookie('admin_setting', 'value', [
'expires' => time() + 86400, // 24 hours
'path' => '/admin/', // Only available in admin section
'httponly' => true
]);
}
/**
* Reading and validating cookies
*/
function readCookies(): void
{
// Basic cookie reading
if (isset($_COOKIE['username'])) {
$username = $_COOKIE['username'];
echo "Welcome back, " . htmlspecialchars($username) . "!\n";
}
// Safe cookie reading with validation
$theme = $_COOKIE['user_preference'] ?? 'light_theme';
$allowedThemes = ['light_theme', 'dark_theme', 'high_contrast'];
if (!in_array($theme, $allowedThemes)) {
$theme = 'light_theme'; // Default fallback
}
echo "Current theme: $theme\n";
// Reading all cookies safely
foreach ($_COOKIE as $name => $value) {
// Always sanitize cookie values
$safeName = htmlspecialchars($name);
$safeValue = htmlspecialchars($value);
echo "Cookie: $safeName = $safeValue\n";
}
}
/**
* Deleting cookies
*/
function deleteCookies(): void
{
// Delete by setting expiration in the past
setcookie('username', '', time() - 3600);
// Delete with same parameters used when setting
setcookie('session_token', '', [
'expires' => time() - 3600,
'path' => '/',
'domain' => '.example.com',
'secure' => true,
'httponly' => true
]);
// Helper function to delete any cookie
function deleteCookie(string $name, string $path = '/', string $domain = ''): void
{
setcookie($name, '', [
'expires' => time() - 3600,
'path' => $path,
'domain' => $domain
]);
}
deleteCookie('user_preference');
}
?>
Advanced Cookie Management
<?php
/**
* Advanced cookie management with security and utility features
*/
class CookieManager
{
private array $defaultOptions;
public function __construct(array $defaultOptions = [])
{
$this->defaultOptions = array_merge([
'expires' => 0, // Session cookie by default
'path' => '/',
'domain' => '',
'secure' => isset($_SERVER['HTTPS']),
'httponly' => true,
'samesite' => 'Lax'
], $defaultOptions);
}
/**
* Set a cookie with encryption for sensitive data
*/
public function setSecure(string $name, string $value, array $options = []): bool
{
$options = array_merge($this->defaultOptions, $options);
// Encrypt sensitive cookie values
$encryptedValue = $this->encrypt($value);
return setcookie($name, $encryptedValue, $options);
}
/**
* Get and decrypt a secure cookie
*/
public function getSecure(string $name): ?string
{
if (!isset($_COOKIE[$name])) {
return null;
}
try {
return $this->decrypt($_COOKIE[$name]);
} catch (Exception $e) {
// Invalid or tampered cookie
$this->delete($name);
return null;
}
}
/**
* Set a cookie with integrity checking
*/
public function setWithSignature(string $name, string $value, array $options = []): bool
{
$options = array_merge($this->defaultOptions, $options);
// Create signed value
$signature = hash_hmac('sha256', $value, $this->getSecretKey());
$signedValue = base64_encode($value . '.' . $signature);
return setcookie($name, $signedValue, $options);
}
/**
* Get a cookie and verify its signature
*/
public function getWithSignature(string $name): ?string
{
if (!isset($_COOKIE[$name])) {
return null;
}
try {
$signedValue = base64_decode($_COOKIE[$name]);
$parts = explode('.', $signedValue, 2);
if (count($parts) !== 2) {
throw new Exception('Invalid signed cookie format');
}
[$value, $signature] = $parts;
$expectedSignature = hash_hmac('sha256', $value, $this->getSecretKey());
if (!hash_equals($expectedSignature, $signature)) {
throw new Exception('Cookie signature verification failed');
}
return $value;
} catch (Exception $e) {
$this->delete($name);
return null;
}
}
/**
* Set a temporary cookie that auto-expires
*/
public function setTemporary(string $name, string $value, int $minutes = 30): bool
{
return $this->set($name, $value, [
'expires' => time() + ($minutes * 60)
]);
}
/**
* Set a persistent cookie that lasts longer
*/
public function setPersistent(string $name, string $value, int $days = 30): bool
{
return $this->set($name, $value, [
'expires' => time() + ($days * 24 * 60 * 60)
]);
}
/**
* Standard cookie setting
*/
public function set(string $name, string $value, array $options = []): bool
{
$options = array_merge($this->defaultOptions, $options);
return setcookie($name, $value, $options);
}
/**
* Get cookie value with default
*/
public function get(string $name, ?string $default = null): ?string
{
return $_COOKIE[$name] ?? $default;
}
/**
* Check if cookie exists
*/
public function has(string $name): bool
{
return isset($_COOKIE[$name]);
}
/**
* Delete a cookie
*/
public function delete(string $name): bool
{
if (isset($_COOKIE[$name])) {
unset($_COOKIE[$name]);
}
return setcookie($name, '', [
'expires' => time() - 3600,
'path' => $this->defaultOptions['path'],
'domain' => $this->defaultOptions['domain']
]);
}
/**
* Clear all cookies
*/
public function clearAll(): void
{
foreach ($_COOKIE as $name => $value) {
$this->delete($name);
}
}
/**
* Simple encryption for cookie values (use a proper encryption library in production)
*/
private function encrypt(string $data): string
{
$key = $this->getSecretKey();
$iv = random_bytes(16);
$encrypted = openssl_encrypt($data, 'AES-256-CBC', $key, 0, $iv);
return base64_encode($iv . $encrypted);
}
/**
* Simple decryption for cookie values
*/
private function decrypt(string $encryptedData): string
{
$key = $this->getSecretKey();
$data = base64_decode($encryptedData);
$iv = substr($data, 0, 16);
$encrypted = substr($data, 16);
$decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);
if ($decrypted === false) {
throw new Exception('Decryption failed');
}
return $decrypted;
}
/**
* Get secret key for encryption/signing (should be stored securely)
*/
private function getSecretKey(): string
{
// In production, store this in environment variables or secure config
return $_ENV['COOKIE_SECRET_KEY'] ?? 'default-secret-key-change-in-production';
}
}
// Usage examples
$cookieManager = new CookieManager([
'secure' => true,
'samesite' => 'Strict'
]);
// Set various types of cookies
$cookieManager->set('theme', 'dark');
$cookieManager->setTemporary('flash_message', 'Welcome!', 5);
$cookieManager->setPersistent('user_id', '123', 30);
$cookieManager->setSecure('sensitive_data', 'secret_value');
$cookieManager->setWithSignature('user_role', 'admin');
// Read cookies
echo "Theme: " . $cookieManager->get('theme', 'light') . "\n";
echo "User ID: " . $cookieManager->get('user_id') . "\n";
echo "Sensitive: " . $cookieManager->getSecure('sensitive_data') . "\n";
echo "Role: " . $cookieManager->getWithSignature('user_role') . "\n";
?>
Sessions in PHP
Basic Session Management
<?php
/**
* PHP Session Management
*
* Sessions provide secure server-side storage for user data
* and are essential for authentication and state management.
*/
/**
* Basic session operations
*/
function demonstrateBasicSessions(): void
{
// Start session (call before any output)
session_start();
// Set session variables
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'john_doe';
$_SESSION['login_time'] = time();
$_SESSION['permissions'] = ['read', 'write'];
// Read session variables
if (isset($_SESSION['user_id'])) {
echo "User ID: " . $_SESSION['user_id'] . "\n";
echo "Username: " . $_SESSION['username'] . "\n";
echo "Login time: " . date('Y-m-d H:i:s', $_SESSION['login_time']) . "\n";
}
// Check if user is logged in
function isLoggedIn(): bool
{
return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']);
}
// Remove specific session variable
unset($_SESSION['permissions']);
// Clear all session data
$_SESSION = [];
// Destroy session completely
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time() - 3600, '/');
}
session_destroy();
}
/**
* Session configuration for security
*/
function configureSecureSessions(): void
{
// Configure session settings before starting
ini_set('session.cookie_httponly', 1); // Prevent XSS
ini_set('session.cookie_secure', 1); // HTTPS only
ini_set('session.cookie_samesite', 'Strict'); // CSRF protection
ini_set('session.use_strict_mode', 1); // Reject uninitialized session IDs
ini_set('session.cookie_lifetime', 0); // Session cookies only
ini_set('session.gc_maxlifetime', 3600); // 1 hour timeout
// Custom session name
session_name('APP_SESSION');
// Start session with security settings
session_start();
// Regenerate session ID for security
if (!isset($_SESSION['initiated'])) {
session_regenerate_id(true);
$_SESSION['initiated'] = true;
}
// Session timeout handling
if (isset($_SESSION['last_activity']) &&
(time() - $_SESSION['last_activity'] > 3600)) {
session_unset();
session_destroy();
session_start();
}
$_SESSION['last_activity'] = time();
}
?>
Advanced Session Management
<?php
/**
* Advanced session management with security features
*/
class SessionManager
{
private int $timeout;
private bool $started = false;
public function __construct(int $timeoutMinutes = 60)
{
$this->timeout = $timeoutMinutes * 60;
$this->configureSession();
}
/**
* Configure session settings for security
*/
private function configureSession(): void
{
// Prevent session fixation attacks
ini_set('session.use_strict_mode', 1);
// Cookie security settings
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', isset($_SERVER['HTTPS']) ? 1 : 0);
ini_set('session.cookie_samesite', 'Strict');
// Session timeout settings
ini_set('session.gc_maxlifetime', $this->timeout);
ini_set('session.cookie_lifetime', 0); // Session cookies
// Use strong session ID generation
ini_set('session.entropy_length', 32);
ini_set('session.hash_function', 'sha256');
// Custom session name
session_name('SECURE_SESSION_' . substr(md5($_SERVER['SERVER_NAME'] ?? 'localhost'), 0, 8));
}
/**
* Start session with security checks
*/
public function start(): bool
{
if ($this->started) {
return true;
}
if (session_start()) {
$this->started = true;
// Validate session
if (!$this->validateSession()) {
$this->destroy();
session_start();
$this->started = true;
}
$this->updateActivity();
return true;
}
return false;
}
/**
* Validate session integrity and timeout
*/
private function validateSession(): bool
{
// Check session timeout
if (isset($_SESSION['last_activity'])) {
if (time() - $_SESSION['last_activity'] > $this->timeout) {
return false;
}
}
// Check browser fingerprint to prevent session hijacking
$currentFingerprint = $this->getBrowserFingerprint();
if (isset($_SESSION['browser_fingerprint'])) {
if ($_SESSION['browser_fingerprint'] !== $currentFingerprint) {
error_log('Session hijacking attempt detected from IP: ' . $_SERVER['REMOTE_ADDR']);
return false;
}
} else {
$_SESSION['browser_fingerprint'] = $currentFingerprint;
}
return true;
}
/**
* Generate browser fingerprint for session validation
*/
private function getBrowserFingerprint(): string
{
$components = [
$_SERVER['HTTP_USER_AGENT'] ?? '',
$_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '',
$_SERVER['HTTP_ACCEPT_ENCODING'] ?? '',
$_SERVER['REMOTE_ADDR'] ?? ''
];
return hash('sha256', implode('|', $components));
}
/**
* Update session activity timestamp
*/
private function updateActivity(): void
{
$_SESSION['last_activity'] = time();
}
/**
* Set session variable
*/
public function set(string $key, $value): void
{
$this->start();
$_SESSION[$key] = $value;
}
/**
* Get session variable
*/
public function get(string $key, $default = null)
{
$this->start();
return $_SESSION[$key] ?? $default;
}
/**
* Check if session variable exists
*/
public function has(string $key): bool
{
$this->start();
return isset($_SESSION[$key]);
}
/**
* Remove session variable
*/
public function remove(string $key): void
{
$this->start();
unset($_SESSION[$key]);
}
/**
* Get all session data
*/
public function all(): array
{
$this->start();
return $_SESSION;
}
/**
* Flash message functionality
*/
public function flash(string $key, string $message): void
{
$this->start();
$_SESSION['_flash'][$key] = $message;
}
/**
* Get and remove flash message
*/
public function getFlash(string $key): ?string
{
$this->start();
$message = $_SESSION['_flash'][$key] ?? null;
unset($_SESSION['_flash'][$key]);
return $message;
}
/**
* Regenerate session ID for security
*/
public function regenerate(): bool
{
if (!$this->started) {
return false;
}
return session_regenerate_id(true);
}
/**
* Destroy session completely
*/
public function destroy(): bool
{
if (!$this->started) {
return true;
}
$_SESSION = [];
// Remove session cookie
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time() - 3600, '/');
}
$result = session_destroy();
$this->started = false;
return $result;
}
/**
* Clear all session data but keep session active
*/
public function clear(): void
{
$this->start();
$_SESSION = [];
}
/**
* Get session information
*/
public function getInfo(): array
{
$this->start();
return [
'id' => session_id(),
'name' => session_name(),
'status' => session_status(),
'cache_limiter' => session_cache_limiter(),
'cache_expire' => session_cache_expire(),
'save_path' => session_save_path(),
'cookie_params' => session_get_cookie_params()
];
}
}
// Usage examples
$session = new SessionManager(120); // 2 hour timeout
// Basic operations
$session->set('user_id', 123);
$session->set('user_data', [
'name' => 'John Doe',
'email' => '[email protected]',
'role' => 'admin'
]);
echo "User ID: " . $session->get('user_id') . "\n";
echo "User name: " . $session->get('user_data')['name'] . "\n";
// Flash messages
$session->flash('success', 'User profile updated successfully!');
$successMessage = $session->getFlash('success');
if ($successMessage) {
echo "Flash: $successMessage\n";
}
// Session info
print_r($session->getInfo());
// Regenerate for security (do this after login)
$session->regenerate();
?>
Security Considerations
Session Security Best Practices
<?php
/**
* Session security implementation with comprehensive protection
*/
class SecureSessionManager extends SessionManager
{
private string $csrfToken;
private array $securityConfig;
public function __construct(array $config = [])
{
$this->securityConfig = array_merge([
'timeout_minutes' => 60,
'ip_validation' => true,
'user_agent_validation' => true,
'csrf_protection' => true,
'session_rotation' => 15, // Rotate session every 15 minutes
], $config);
parent::__construct($this->securityConfig['timeout_minutes']);
}
/**
* Enhanced session validation with additional security checks
*/
protected function validateSession(): bool
{
if (!parent::validateSession()) {
return false;
}
// IP address validation
if ($this->securityConfig['ip_validation']) {
if (!$this->validateIpAddress()) {
return false;
}
}
// User agent validation
if ($this->securityConfig['user_agent_validation']) {
if (!$this->validateUserAgent()) {
return false;
}
}
// Session rotation for security
if ($this->shouldRotateSession()) {
$this->regenerate();
$_SESSION['last_rotation'] = time();
}
return true;
}
/**
* Validate IP address hasn't changed
*/
private function validateIpAddress(): bool
{
$currentIp = $_SERVER['REMOTE_ADDR'] ?? '';
if (!isset($_SESSION['ip_address'])) {
$_SESSION['ip_address'] = $currentIp;
return true;
}
if ($_SESSION['ip_address'] !== $currentIp) {
error_log("Session IP change detected: {$_SESSION['ip_address']} -> $currentIp");
return false;
}
return true;
}
/**
* Validate user agent hasn't changed significantly
*/
private function validateUserAgent(): bool
{
$currentUserAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (!isset($_SESSION['user_agent_hash'])) {
$_SESSION['user_agent_hash'] = hash('sha256', $currentUserAgent);
return true;
}
$currentHash = hash('sha256', $currentUserAgent);
if ($_SESSION['user_agent_hash'] !== $currentHash) {
error_log("Session user agent change detected");
return false;
}
return true;
}
/**
* Check if session should be rotated
*/
private function shouldRotateSession(): bool
{
if (!isset($_SESSION['last_rotation'])) {
$_SESSION['last_rotation'] = time();
return false;
}
$rotationInterval = $this->securityConfig['session_rotation'] * 60;
return (time() - $_SESSION['last_rotation']) > $rotationInterval;
}
/**
* Generate CSRF token
*/
public function generateCSRFToken(): string
{
if (!$this->securityConfig['csrf_protection']) {
return '';
}
$this->start();
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
/**
* Validate CSRF token
*/
public function validateCSRFToken(string $token): bool
{
if (!$this->securityConfig['csrf_protection']) {
return true;
}
$this->start();
return isset($_SESSION['csrf_token']) &&
hash_equals($_SESSION['csrf_token'], $token);
}
/**
* Login with enhanced security
*/
public function login(int $userId, array $userData = []): void
{
$this->start();
// Regenerate session ID to prevent fixation
$this->regenerate();
// Store user information
$_SESSION['user_id'] = $userId;
$_SESSION['user_data'] = $userData;
$_SESSION['login_time'] = time();
$_SESSION['last_rotation'] = time();
// Generate new CSRF token
if ($this->securityConfig['csrf_protection']) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// Log successful login
error_log("Successful login for user $userId from IP: " . $_SERVER['REMOTE_ADDR']);
}
/**
* Logout with complete cleanup
*/
public function logout(): void
{
$this->start();
$userId = $_SESSION['user_id'] ?? 'unknown';
// Clear all session data
$this->clear();
// Destroy session
$this->destroy();
// Log logout
error_log("User $userId logged out from IP: " . $_SERVER['REMOTE_ADDR']);
}
/**
* Check if user is authenticated
*/
public function isAuthenticated(): bool
{
$this->start();
return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']);
}
/**
* Get authenticated user ID
*/
public function getUserId(): ?int
{
$this->start();
return $_SESSION['user_id'] ?? null;
}
/**
* Get user data
*/
public function getUserData(): array
{
$this->start();
return $_SESSION['user_data'] ?? [];
}
}
// Usage example with security features
$secureSession = new SecureSessionManager([
'timeout_minutes' => 30,
'ip_validation' => true,
'csrf_protection' => true,
'session_rotation' => 10
]);
// Simulate login
$secureSession->login(123, [
'name' => 'John Doe',
'email' => '[email protected]',
'role' => 'admin'
]);
// CSRF protection example
$csrfToken = $secureSession->generateCSRFToken();
echo "CSRF Token: $csrfToken\n";
// In form processing
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$submittedToken = $_POST['csrf_token'] ?? '';
if (!$secureSession->validateCSRFToken($submittedToken)) {
die('CSRF token validation failed');
}
// Process form safely
}
// Check authentication
if ($secureSession->isAuthenticated()) {
echo "User is logged in: " . $secureSession->getUserId() . "\n";
} else {
echo "User is not authenticated\n";
}
?>
Related Topics
For more web development and security topics:
- PHP CSRF Protection - Cross-site request forgery prevention
- PHP Authentication Systems - Complete authentication implementation
- PHP Input Validation - Validating user input securely
- PHP Web Security - Comprehensive web security practices
- PHP Forms - Form handling and processing
Summary
Sessions and cookies are fundamental for web application state management:
Cookies: Client-side storage for small amounts of data, perfect for user preferences and simple state information.
Sessions: Server-side storage for larger, more sensitive data with enhanced security through server-side control.
Security Best Practices: Always use HTTPS, implement proper timeout handling, validate session integrity, and protect against common attacks.
Modern Approach: Combine secure session configuration with proper validation, CSRF protection, and regular security auditing.
Understanding these concepts enables you to build secure, user-friendly web applications that maintain state effectively while protecting user data and preventing security vulnerabilities.