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:
<?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.
$ 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=/; secureTo 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.