, e.g. // using a short echo tag right after the form opening. csrf_field() returns // the full hidden input markup. // // Usage in a POST handler (before any side-effects): call csrf_check(). On // failure it sends 403 and exits — never returns to the caller. // // SameSite=Lax already blocks the common "image-form" cross-site POST attack, // but does NOT protect against same-site XSS or an attacker that controls any // sibling subdomain under the .andmore.pt cookie scope. That's what this is // for. // // NOTE: do NOT include the literal short-echo close-tag inside comments in // this file. PHP closes the script block at the first such sequence even when // it appears inside a // comment, and any whitespace after it gets emitted as // HTML output — which then breaks header() with "headers already sent". function csrf_token(): string { if (empty($_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return $_SESSION['csrf_token']; } function csrf_field(): string { return ''; } function csrf_check(): void { if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') return; $supplied = $_POST['_csrf'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''; $expected = $_SESSION['csrf_token'] ?? ''; if (!is_string($supplied) || $expected === '' || !hash_equals($expected, $supplied)) { http_response_code(403); header('Content-Type: text/html; charset=utf-8'); echo '