Implementing a Session Middleware in Slim 4 Framework
What is Session Middleware?
Section titled “What is Session Middleware?”- Session middleware automatically starts PHP sessions for your entire application.
- Instead of calling session_start()in every route, the middleware does it for you once: before any route executes.
Why use it?
- ✅ Write session startup code once.
- ✅ No risk of forgetting session_start().
- ✅ No errors from starting sessions multiple times.
- ✅ Sessions available everywhere automatically.
Creating Session Middleware
Section titled “Creating Session Middleware”To properly implement session middleware, we’ll need to create and use two components:
- SessionManager: Handles session configuration and security.
- SessionMiddleware: Starts or resumes sessions for every request.
Step 1: Create the SessionManager Class
Section titled “Step 1: Create the SessionManager Class”The SessionManager class will handle the session configuration and security.
- It must be created in a file named SessionManager.phpin theapp/Helpers/directory.
<?php
namespace App\Helpers;
class SessionManager{    /**     * Start the session with secure configuration.     */    public static function start(): void    {        if (session_status() === PHP_SESSION_NONE) {
            // Configure session to last 1 day (good for local development)            $sessionLifetime = 24 * 60 * 60; // (hours * minutes * seconds) -> 1 day in seconds -> 86400 seconds
            // Set session cookie parameters            session_set_cookie_params([                'lifetime' => $sessionLifetime,                'path' => '/',                'domain' => '',                'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',                'httponly' => true,                'samesite' => 'Strict'            ]);
            // Configure garbage collection (automatic cleanup)            ini_set('session.gc_maxlifetime', $sessionLifetime);            ini_set('session.gc_probability', 1);            ini_set('session.gc_divisor', 100);
            // Security settings            ini_set('session.use_strict_mode', 1);            ini_set('session.cookie_httponly', 1);
            session_start();
            // Regenerate session ID every 15 minutes for security            if (!isset($_SESSION['last_regeneration'])) {                $_SESSION['last_regeneration'] = time();                session_regenerate_id(true);            } elseif (time() - $_SESSION['last_regeneration'] > 900) {                $_SESSION['last_regeneration'] = time();                session_regenerate_id(true);            }
            // Browser fingerprinting for security (detect session hijacking)            $fingerprint = self::generateFingerprint();            if (!isset($_SESSION['fingerprint'])) {                $_SESSION['fingerprint'] = $fingerprint;            } elseif ($_SESSION['fingerprint'] !== $fingerprint) {                // Different browser detected - destroy and restart session                session_destroy();                session_start();                $_SESSION['fingerprint'] = $fingerprint;                $_SESSION['last_regeneration'] = time();            }        }    }
    /**     * Generate a unique browser fingerprint for security.     */    private static function generateFingerprint(): string    {        $factors = [            $_SERVER['HTTP_USER_AGENT'] ?? '',            $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '',            $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '',            $_SERVER['REMOTE_ADDR'] ?? ''        ];
        return hash('sha256', implode('|', $factors));    }
    /**     * Store a value in the session.     */    public static function set(string $key, $value): void    {        $_SESSION[$key] = $value;    }
    /**     * Retrieve a value from the session.     */    public static function get(string $key, $default = null)    {        return $_SESSION[$key] ?? $default;    }
    /**     * Check if a key exists in the session.     */    public static function has(string $key): bool    {        return isset($_SESSION[$key]);    }
    /**     * Remove a key from the session.     */    public static function remove(string $key): void    {        unset($_SESSION[$key]);    }
    /**     * Clear all session data.     */    public static function clear(): void    {        session_unset();    }
    /**     * Destroy the session completely.     */    public static function destroy(): void    {        session_destroy();    }}Key functionalities:
- 1-day session lifetime: Sessions last 24 hours (convenient for development).
- Secure cookie settings: Protects against XSS and ensures HTTPS when available.
- Garbage collection: Automatically cleans up old session files (1% chance per request).
- Session ID regeneration: Changes session ID every 15 minutes to prevent hijacking.
- Browser fingerprinting: Detects if session cookie is stolen and used on different browser.
- Helper methods: Convenient methods to work with session data:
| Method | Description | 
|---|---|
| set($key, $value) | Store data in the session | 
| get($key, $default) | Retrieve data from the session (returns default if key doesn’t exist) | 
| has($key) | Check if a key exists in the session | 
| remove($key) | Remove a specific key from the session | 
| clear() | Clear all session data (keeps session alive) | 
| destroy() | Destroy the session completely | 
Step 2: Create the Middleware
Section titled “Step 2: Create the Middleware”The SessionMiddleware class will start or resume sessions for every request.
- It must be created in a file named SessionMiddleware.phpin theapp/Middleware/directory.
<?php
namespace App\Middleware;
use App\Helpers\SessionManager;use Psr\Http\Server\MiddlewareInterface;use Psr\Http\Message\ResponseInterface as Response;use Psr\Http\Message\ServerRequestInterface as Request;use Psr\Http\Server\RequestHandlerInterface as Handler;
class SessionMiddleware implements MiddlewareInterface{    public function process(        Request $request,        Handler $handler    ): Response    {        // Start session using SessionManager        SessionManager::start();
        // Continue to next middleware/route        return $handler->handle($request);    }}How it works?
- Middleware calls SessionManager::start()for every request
- SessionManager handles all configuration and security automatically
- Session is ready to use in all routes
Step 3: Register the Middleware
Section titled “Step 3: Register the Middleware”To register the session middleware, we need to create an instance of the SessionMiddleware class and add it to the application.
This should be done in the (config/middleware.php) file:
// Add the session middleware to the application (applies to ALL routes).$app->add(SessionMiddleware::class);That’s it! Sessions now work in all your routes.
Step 4: Testing Your Session Middleware
Section titled “Step 4: Testing Your Session Middleware”Add a new route to test the session middleware. This route will increment a counter and store it in the session.
The following code should be added to the web-routes.php file:
use App\Helpers\SessionManager;
$app->get('/test-session', function ($request, $response) {    // Get current counter, increment it.    $counter = SessionManager::get('counter', 0) + 1;    SessionManager::set('counter', $counter);
    $response->getBody()->write("Counter: " . $counter);
    return $response;});Or using traditional $_SESSION:
$app->get('/test-session', function ($request, $response) {    $_SESSION['counter'] = ($_SESSION['counter'] ?? 0) + 1;
    $response->getBody()->write(        "Counter: " . $_SESSION['counter']    );
    return $response;});Expected results:
- Visit /test-session→ Counter: 1
- Refresh page → Counter: 2
- Refresh again → Counter: 3
If it always shows “Counter: 1”, sessions aren’t persisting (see troubleshooting below).
Using Sessions in Routes
Section titled “Using Sessions in Routes”Once registered, you can use session data in two ways:
Option 1: Using SessionManager (Recommended)
Section titled “Option 1: Using SessionManager (Recommended)”Cleaner and more object-oriented approach using controller methods:
use App\Helpers\SessionManager;use App\Controllers\BaseController;use Psr\Http\Message\ResponseInterface as Response;use Psr\Http\Message\ServerRequestInterface as Request;
class AuthController extends BaseController{    // Store data    public function login(Request $request, Response $response, array $args): Response    {        // Process login logic here        SessionManager::set('user_id', 123);        SessionManager::set('username', 'john');
        // Redirect to dashboard after login        return $this->redirect($request, $response, 'dashboard');    }
    // Read data    public function profile(Request $request, Response $response, array $args): Response    {        $userId = SessionManager::get('user_id');
        if (!$userId) {            // Not logged in - redirect to login page            return $this->redirect($request, $response, 'login');        }
        // User is logged in - render profile view        $username = SessionManager::get('username');
        return $this->render($response, 'auth/profile.php', [            'user_id' => $userId,            'username' => $username        ]);    }
    // Check if logged in    public function dashboard(Request $request, Response $response, array $args): Response    {        if (!SessionManager::has('user_id')) {            // Not logged in - redirect to login page            return $this->redirect($request, $response, 'login');        }
        // User is logged in - render dashboard        return $this->render($response, 'auth/dashboard.php', [            'username' => SessionManager::get('username')        ]);    }
    // Logout (destroy session)    public function logout(Request $request, Response $response, array $args): Response    {        SessionManager::destroy();
        // Redirect to home page after logout        return $this->redirect($request, $response, 'home.index');    }}
class CartController extends BaseController{    // Remove specific key    public function clearCart(Request $request, Response $response, array $args): Response    {        SessionManager::remove('cart_items');
        // Redirect back to cart page        return $this->redirect($request, $response, 'cart.index');    }}Option 2: Using $_SESSION Directly
Section titled “Option 2: Using $_SESSION Directly”Traditional PHP approach (still works):
// Store data$app->post('/login', function ($request, $response) {    $_SESSION['user_id'] = 123;    $_SESSION['username'] = 'john';    return $response;});
// Read data$app->get('/profile', function ($request, $response) {    $userId = $_SESSION['user_id'] ?? null;
    if (!$userId) {        return $response->withStatus(401);    }
    return $response;});
// Logout$app->post('/logout', function ($request, $response) {    session_destroy();    return $response;});Recommendation: Use SessionManager methods for cleaner, more maintainable code.
Key Best Practices
Section titled “Key Best Practices”1. Use SessionManager Methods (Recommended)
Section titled “1. Use SessionManager Methods (Recommended)”use App\Helpers\SessionManager;
// ✅ Best - clean and safe$userId = SessionManager::get('user_id');SessionManager::set('cart_total', 99.99);
// ✅ Good - with default value$userId = SessionManager::get('user_id', null);
// ✅ Check if existsif (SessionManager::has('user_id')) {    // User is logged in}Or using $_SESSION directly:
// ✅ Good - prevents errors$userId = $_SESSION['user_id'] ?? null;
// ❌ Bad - throws error if not set$userId = $_SESSION['user_id'];2. Use Clear Session Key Names
Section titled “2. Use Clear Session Key Names”// ✅ GoodSessionManager::set('user_id', 123);SessionManager::set('cart_items', []);
// ❌ BadSessionManager::set('u', 123);SessionManager::set('data', []);3. Clean Up on Logout
Section titled “3. Clean Up on Logout”use App\Helpers\SessionManager;
// ✅ Recommended - clear all data and destroySessionManager::destroy();
// ✅ Alternative - just clear dataSessionManager::clear();
// Traditional way (still works)session_unset();session_destroy();SessionManager Quick Reference
Section titled “SessionManager Quick Reference”All available methods for working with sessions:
use App\Helpers\SessionManager;
// Start session (called automatically by middleware)SessionManager::start();
// Store dataSessionManager::set('key', 'value');SessionManager::set('user_id', 123);SessionManager::set('cart', ['item1', 'item2']);
// Retrieve data$value = SessionManager::get('key');                    // Returns value or null$userId = SessionManager::get('user_id', 0);           // Returns value or default (0)$cart = SessionManager::get('cart', []);               // Returns array or empty array
// Check if key existsif (SessionManager::has('user_id')) {    // Key exists in session}
// Remove specific keySessionManager::remove('cart');SessionManager::remove('user_id');
// Clear all session data (but keep session active)SessionManager::clear();
// Destroy session completely (logout)SessionManager::destroy();Common patterns:
// LoginSessionManager::set('user_id', $user->id);SessionManager::set('username', $user->username);
// Check if logged inif (!SessionManager::has('user_id')) {    // Redirect to login}
// Get user data$userId = SessionManager::get('user_id');$username = SessionManager::get('username', 'Guest');
// LogoutSessionManager::destroy();Summary of What Has Been Done:
Section titled “Summary of What Has Been Done:”What you created:
- 
SessionManager ( app/Helpers/SessionManager.php)- Handles all session configuration and security settings.
- Includes automatic session ID regeneration.
- Detects and prevents session hijacking.
- Provides convenient helper methods: set(),get(),has(),remove(),clear(),destroy().
 
- 
SessionMiddleware ( app/Middleware/SessionMiddleware.php)- Triggers SessionManager for every request.
- Makes sessions available in all routes.
 
- 
Registered middleware in config/middleware.php
Common Issues
Section titled “Common Issues”Issue 1: Sessions Not Persisting
Section titled “Issue 1: Sessions Not Persisting”Symptoms: Counter always shows 1, session data disappears
Solutions:
- ✅ Verify middleware is registered in config/middleware.php
- ✅ Check browser accepts cookies (check browser settings)
- ✅ Ensure session save path is writable: ls -la /tmp(Linux/Mac) or checkC:\Windows\Temp(Windows)
Issue 2: Fingerprint Mismatch (Session Resets)
Section titled “Issue 2: Fingerprint Mismatch (Session Resets)”Symptoms: Session resets unexpectedly, user logged out randomly
Causes:
- Browser extensions changing headers
- VPN switching IP addresses
- Mobile switching between WiFi/cellular
Solutions:
- For development, comment out the fingerprinting code in SessionManager::start()(lines 85-95)
- For production, keep it enabled for security
Issue 3: Session Expires Too Quickly
Section titled “Issue 3: Session Expires Too Quickly”Symptoms: Session expires before 24 hours
Solution:
// Change session lifetime in SessionManager.php$sessionLifetime = 7 * 24 * 60 * 60; // 7 days instead of 1 day