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