Session fixation, CSRF tokens, remember-me rotation, multi-site isolation, and the 2-hour absolute timeout — every security layer in our auth system explained.
Why Session Security Is Hard
PHP sessions seem simple: session_start(), store data in $_SESSION, done. But that simplicity hides a minefield of security issues. Session fixation, session hijacking, cross-site session leakage, CSRF attacks — each one can give an attacker full admin access to your CMS.
JekCMS implements seven layers of session security. Here is each one with the specific attack it prevents.
Layer 1: Secure Cookie Configuration
// Session.php constructor
ini_set('session.cookie_httponly', 1); // Prevent JavaScript access
ini_set('session.cookie_secure', IS_PRODUCTION ? 1 : 0); // HTTPS only in production
ini_set('session.cookie_samesite', 'Lax'); // Prevent CSRF via cross-origin requests
ini_set('session.use_strict_mode', 1); // Reject uninitialized session IDs
ini_set('session.use_only_cookies', 1); // No session ID in URLs
Attack prevented: httponly stops XSS from stealing session cookies. secure prevents session cookies from being sent over unencrypted HTTP. samesite=Lax blocks most CSRF attacks by not sending cookies with cross-origin POST requests.
Layer 2: Session Fixation Prevention
Session fixation is when an attacker sets a known session ID before the victim logs in. After login, the attacker uses the same session ID to access the authenticated session.
// After successful login
session_regenerate_id(true); // Generate new ID, delete old session
$_SESSION['created_at'] = time();
$_SESSION['user_id'] = $user['id'];
session_regenerate_id(true) creates a new session ID and destroys the old session data. The attacker's known session ID becomes invalid.
Layer 3: CSRF Token Validation
// Generate token (once per session)
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// Validate on form submission
function validate_csrf(string $token): bool {
return hash_equals($_SESSION['csrf_token'] ?? '', $token);
}
Every form includes a hidden csrf_token field. POST requests without a valid token are rejected with a 403.
Layer 4: Remember-Me Token Rotation
The "remember me" feature uses a separate token stored in a cookie and the database. Each time the token is used to authenticate, it is rotated — the old token is invalidated and a new one is issued:
public function validateRememberToken(string $token): ?array
{
$record = $this->db->fetch(
"SELECT * FROM remember_tokens WHERE token = ? AND expires_at > NOW()",
[hash('sha256', $token)]
);
if ($record) {
// Rotate: delete old, create new
$this->db->delete('remember_tokens', 'id = ?', [$record['id']]);
$newToken = $this->createRememberToken($record['user_id']);
return ['user_id' => $record['user_id'], 'new_token' => $newToken];
}
return null;
}
Token rotation means a stolen remember-me cookie can only be used once. If the legitimate user visits the site before the attacker, the attacker's token is already invalid.
Layer 5: Multi-Site Session Isolation
JekCMS runs 12 sites on the same server. Without isolation, logging into Site A could give you access to Site B if they share the same PHP session storage. We prevent this with two mechanisms:
// 1. Unique session name per site
session_name('JEKCMS_' . md5(SITE_URL));
// 2. Site hash verification in session
$_SESSION['_site_hash'] = md5(SITE_URL);
// On every request
if ($_SESSION['_site_hash'] !== md5(SITE_URL)) {
session_destroy();
redirect(SITE_URL . '/admin/login.php');
}
Layer 6: Absolute Session Timeout
Sessions expire after 2 hours regardless of activity. This limits the window of opportunity for session hijacking:
$maxLifetime = 7200; // 2 hours
if (isset($_SESSION['created_at']) && (time() - $_SESSION['created_at']) > $maxLifetime) {
session_destroy();
redirect(SITE_URL . '/admin/login.php?expired=1');
}
Layer 7: IP Binding (Optional)
For high-security deployments, sessions can be bound to the client's IP address. If the IP changes mid-session, the session is invalidated. This is disabled by default because mobile users frequently change IPs, but it can be enabled in config.