Fix admin login: replace PHP sessions with HMAC cookie auth

PHP sessions were unreliable on this host — the web process could write
session files but LiteSpeed served cached login-page responses on the
redirect, bypassing PHP entirely.

Replace sessions with a self-contained signed cookie:
- On login: generate random 32-byte token + expiry, sign with HMAC-SHA256
- On each request: verify signature and expiry — no filesystem reads needed
- Cookie: Secure, HttpOnly, SameSite=Lax, path=/admin/, 24h expiry
- admin/.htaccess: CacheEnable off + no-store headers to prevent LiteSpeed
  from caching admin responses

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 14:15:33 +00:00
parent b3b831e4a0
commit 8f5362aa95
2 changed files with 43 additions and 9 deletions
+5
View File
@@ -0,0 +1,5 @@
<IfModule LiteSpeed>
CacheEnable off
</IfModule>
Header always set Cache-Control "no-store, no-cache, must-revalidate"
Header always set Pragma "no-cache"
+38 -9
View File
@@ -1,23 +1,52 @@
<?php <?php
require_once dirname(__DIR__) . '/db.php'; require_once dirname(__DIR__) . '/db.php';
ini_set('session.save_path', '/home/parkerslingshotrentals.com/sessions');
ini_set('session.cookie_httponly', 1); // ── Cookie-based auth (no PHP sessions — avoids server caching/permission issues) ──
ini_set('session.cookie_samesite', 'Lax'); define('AUTH_COOKIE', 'parker_auth');
session_start(); define('AUTH_SECRET', hash('sha256', ADMIN_PASS . ADMIN_SESSION_KEY)); // not reversible
function _authToken(): string {
$t = bin2hex(random_bytes(32));
$e = time() + 86400; // 24h
$s = hash_hmac('sha256', "$t|$e", AUTH_SECRET);
return "$t.$e.$s";
}
function _verifyAuth(): bool {
$c = $_COOKIE[AUTH_COOKIE] ?? '';
$p = explode('.', $c, 3);
if (count($p) !== 3) return false;
[$t, $e, $s] = $p;
if ((int)$e < time()) return false;
return hash_equals(hash_hmac('sha256', "$t|$e", AUTH_SECRET), $s);
}
function _setAuth(): void {
$secure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
setcookie(AUTH_COOKIE, _authToken(), [
'expires' => time() + 86400,
'path' => '/admin/',
'secure' => $secure,
'httponly' => true,
'samesite' => 'Lax',
]);
}
function _clearAuth(): void {
$secure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
setcookie(AUTH_COOKIE, '', ['expires' => time()-3600, 'path' => '/admin/', 'secure' => $secure, 'httponly' => true, 'samesite' => 'Lax']);
}
$isAjax = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) || (($_SERVER['HTTP_ACCEPT'] ?? '') === 'application/json'); $isAjax = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) || (($_SERVER['HTTP_ACCEPT'] ?? '') === 'application/json');
// ── Auth ────────────────────────────────────────────────────────────────────── // ── Auth ──────────────────────────────────────────────────────────────────────
if ($_POST['action'] ?? '' === 'login') { if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'login') {
if ($_POST['username'] === ADMIN_USER && password_verify($_POST['password'] ?? '', ADMIN_PASS)) { if ($_POST['username'] === ADMIN_USER && password_verify($_POST['password'] ?? '', ADMIN_PASS)) {
$_SESSION[ADMIN_SESSION_KEY] = true; _setAuth();
} }
header('Location: /admin/'); exit; header('Location: /admin/'); exit;
} }
if ($_GET['action'] ?? '' === 'logout') { if (($_GET['action'] ?? '') === 'logout') {
session_destroy(); header('Location: /admin/'); exit; _clearAuth(); header('Location: /admin/'); exit;
} }
$authed = !empty($_SESSION[ADMIN_SESSION_KEY]); $authed = _verifyAuth();
// ── AJAX handlers ───────────────────────────────────────────────────────────── // ── AJAX handlers ─────────────────────────────────────────────────────────────
if ($isAjax && !$authed) { if ($isAjax && !$authed) {