Contents
PHP Cookie Flags Explained
Fellow developers, picture this: it's 2 AM, your app's humming along in production, but suddenly logs light up with session hijacking attempts. A cookie slipped through without the right flags, and now attackers are sniffing around user sessions. I've been there—staring at the screen, coffee gone cold, wondering how a tiny oversight turned into a security nightmare. Cookies are the unsung heroes (or villains) of state management in PHP, but their flags are what keep them safe. Today, we're diving deep into these flags: Secure, HttpOnly, and SameSite. They're not just checkboxes; they're your first line of defense against XSS, CSRF, and MITM attacks.
Why care now? PHP's setcookie() has evolved—since PHP 7.3, setting these flags is straightforward with an options array. But get them wrong, and you're leaving doors wide open. Let's break it down, with code you can copy-paste tomorrow, real-world pitfalls I've hit, and that quiet satisfaction of locking things down.
The Basics: How PHP Handles Cookies
Before flags, remember the foundation. Cookies store data client-side, sent back with every request. In PHP, setcookie() fires before any HTML output—headers only.
setcookie("user", "John Doe", time() + (86400 * 30), "/"); // Expires in 30 days, site-wide
Grab it via $_COOKIE['user']. Simple. But PHP tweaks names (dots become underscores), and $_SERVER['HTTP_COOKIE'] shows raw headers if you need originals. That's table stakes. Flags? They add security layers.
I've burned hours debugging why $_COOKIE misses values—turns out, multi-subdomain cookies or encoding quirks. Pro tip: for raw access, parse $_SERVER['HTTP_COOKIE'] like this:
$cookies = explode('; ', $_SERVER['HTTP_COOKIE']);
foreach ($cookies as $cookie) {
[$name, $value] = explode('=', $cookie, 2);
echo trim($name) . ' => ' . trim($value);
}
Now, the flags that matter.
Secure Flag: HTTPS or Bust
Ever sent a session cookie over plain HTTP? It's like mailing your house keys. The Secure flag says: "Only send this over HTTPS." Browsers ignore it on HTTP anyway, but set it explicitly.
In PHP 7.3+:
setcookie("sessionid", "abc123", [
'expires' => time() + 3600,
'path' => '/',
'secure' => true, // Only HTTPS
'httponly' => true
]);
Or globally for sessions: ini_set('session.cookie_secure', 1);. Why? Man-in-the-middle attacks feast on unencrypted cookies. I once audited a client's site—mixed content warnings everywhere, cookies leaking on HTTP redirects. Fixed with Secure, and traffic felt safer instantly.
On localhost? Use false for domain, or browsers balk at single-dot domains.
Have you checked your prod setup? Force HTTPS redirects, then Secure flags. It's non-negotiable for compliance like GDPR.
HttpOnly Flag: No JavaScript Peeking
XSS is ruthless. Attackers inject script, grab document.cookie, steal sessions. HttpOnly blocks that: "Server only, browser—hands off."
Same code:
setcookie("sessionid", "abc123", [
'expires' => time() + 3600,
'path' => '/',
'secure' => true,
'httponly' => true // No JS access
]);
Apps rarely need JS cookie reads. Analytics? Use localStorage. Sessions? HttpOnly always. I remember a pentest where XSS pilfered non-HttpOnly carts—users lost items mid-checkout. Heartbreaking. Flip this flag, and JavaScript's blind.
Combine with Secure for session cookies. PHP sessions auto-respect session.cookie_httponly = 1 in php.ini.
SameSite Flag: CSRF's Kryptonite
Cross-site request forgery: user logged in, clicks malicious link, boom—actions fire on your site. SameSite controls cross-site sends.
Three modes:
- Strict: Cookie only if already on your site. Click from elsewhere? No cookie. Ironclad, but breaks some links.
- Lax: Safe GETs (links) work cross-site; no embeds or POSTs. Balanced default.
- None: Full cross-site, but needs Secure.
PHP example:
// Paranoia mode
setcookie("super_secure", "value", ['samesite' => 'Strict', 'secure' => true, 'httponly' => true]);
// Everyday sessions
setcookie("session", "xyz", ['samesite' => 'Lax', 'secure' => true, 'httponly' => true]);
// Legacy embeds (rare)
setcookie("analytics", "data", ['samesite' => 'None', 'secure' => true]);
Since PHP 7.3, it's in the options array—order matters if no keys. Browsers default to Lax now, but set it explicitly. I tweaked a forum app from None to Lax—CSRF logs vanished overnight.
Real-World Pitfalls and Debugging
Friends, theory's great, but code bites back. Here's where I've stumbled.
First, timing. setcookie() before <html>. Output buffering? ob_start() early.
Domain quirks: Localhost needs false. Prod? $_SERVER['HTTP_HOST'].
$domain = ($_SERVER['HTTP_HOST'] !== 'localhost') ? $_SERVER['HTTP_HOST'] : false;
setcookie('test', 'value', ['expires' => time() + 3600, 'domain' => $domain, 'secure' => true]);
Deleting cookies? Set expiry to past:
if (isset($_SERVER['HTTP_COOKIE'])) {
$cookies = explode('; ', $_SERVER['HTTP_COOKIE']);
foreach ($cookies as $cookie) {
$parts = explode('=', $cookie, 2);
setcookie(trim($parts[0]), '', time() - 3600, '/');
}
}
Options array gotchas: Keys ignored in some orders pre-7.3. Stick to named:
$options = [
'expires' => time() + 86400,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
];
setcookie('secure_cookie', 'value', $options);
Test it. DevTools > Application > Cookies. See flags? Good. Curl headers? Better.
When to Use What: A Quick Guide
- Sessions: Secure + HttpOnly + SameSite=Lax. Always.
- Preferences: HttpOnly false if JS needs it, but Lax/Secure.
- Analytics: SameSite=None + Secure, partitioned if Chrome complains.
Edge cases: Multi-subdomain? Set domain to .example.com. PHP picks one in $_COOKIE, parse raw for all.
Beyond Flags: Full Cookie Security
Flags aren't solo. Validate inputs, regenerate IDs on login, short expiries. Pair with CSP headers against XSS.
I've refactored legacy code—non-secure cookies everywhere. Post-fix? Clean audits, happier sleep.
Colleagues, implementing these feels like boarding up windows before a storm. Not flashy, but when attacks glance off, that quiet confidence sticks. Next time you setcookie(), add the flags. Your users—and future you—will thank you in the glow of a stable monitor.