1. php
  2. /web development
  3. /sessions-cookies

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

<?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');
}
?>
<?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";
}
?>

For more web development and security topics:

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.