Cookies

Setting, Reading and Deleting Cookies

setcookie() takes an options array (PHP 7.3+) with the keys expires, path, domain, secure, httponly, samesite and, new in 8.5, partitioned; unknown keys throw a ValueError. Without expires the cookie dies with the browser. This "remember me" cookie signs the user ID and expiry with an HMAC (Hashing and HMAC), so an edited cookie is ignored:

remember.php: a signed remember-me cookiePHP
<?php
declare(strict_types=1);
$key = getenv('APP_KEY') ?: exit("APP_KEY not set\n");      // SetEnv in the vhost, not code
$sign = fn(string $data): string => hash_hmac('sha256', $data, $key);
if (($_GET['do'] ?? '') === 'login') {                      // after the password checked out
  $value = '42.' . ($exp = time() + 30 * 86400);            // user ID and expiry
  setcookie('remember', "$value." . $sign($value), ['expires' => $exp, 'path' => '/',
    'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);
} elseif (($_GET['do'] ?? '') === 'forget') {
  setcookie('remember', '', ['path' => '/', 'secure' => true]);
} else {
  [$id, $exp, $sig] = explode('.', $_COOKIE['remember'] ?? '', 3) + ['', '', ''];
  $valid = hash_equals($sign("$id.$exp"), $sig) && (int) $exp > time();
  echo 'user: ', $valid ? $id : 'guest', "\n";
}

The first command defines a print helper, used through this section, that folds Set-Cookie lines.

Output of 151
$ fold_cookies() { tr -d '\r' | \
  sed -E 's/([0-9a-f]{8})[0-9a-f]{40,}/\1.../; s/; (expires|path)=/;\n    \1=/g'; }
$ curl -si -c jar.txt '127.0.0.1:8215/remember.php?do=login' | grep -i ^set-cookie \
  | fold_cookies
Set-Cookie: remember=42.1792744219.686688ea...;
    expires=Fri, 23 Oct 2026 08:30:19 GMT; Max-Age=2592000;
    path=/; secure; HttpOnly; SameSite=Lax
$ curl -s -b jar.txt 127.0.0.1:8215/remember.php
user: 42
$ curl -s -b 'remember=1.4102444800.forged' 127.0.0.1:8215/remember.php
user: guest
$ curl -si -b jar.txt -c jar.txt '127.0.0.1:8215/remember.php?do=forget' | grep -i ^set-cookie
Set-Cookie: remember=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/; secure

To delete a cookie, overwrite it with a past expiry, as PHP does for an empty value; path and domain must match the original. A signed cookie cannot be revoked early; for "log out everywhere," store a random per-device token in a table instead.