Skip to content

Commit 23ef54a

Browse files
committed
Response: setCookie() forces Secure for SameSite=None
A cookie with SameSite=None is rejected by browsers unless it also carries the Secure attribute (RFC 6265bis). setCookie() now enables Secure automatically in that case, overriding both the $secure argument and the cookieSecure default.
1 parent 6b040d9 commit 23ef54a

2 files changed

Lines changed: 14 additions & 2 deletions

File tree

src/Http/Response.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,15 +253,19 @@ public function setCookie(
253253
}
254254

255255
$seconds = Helpers::expirationToSeconds($expire);
256+
$sameSite ??= self::SameSiteLax;
257+
$secure = $sameSite === self::SameSiteNone
258+
? true // SameSite=None requires the Secure attribute, otherwise the browser rejects the cookie
259+
: $secure ?? $this->cookieSecure;
256260
// the value is raw-url-encoded the same way PHP reads it back from $_COOKIE;
257261
// Max-Age takes precedence over expires (RFC 6265), expires is sent too for ancient clients
258262
$cookie = $name . '=' . rawurlencode($value)
259263
. ($seconds === null ? '' : '; expires=' . Helpers::formatDate(time() + $seconds) . '; Max-Age=' . max(0, $seconds))
260264
. '; path=' . ($path ?? ($domain ? '/' : $this->cookiePath))
261265
. (($domain = $domain ?? ($path ? '' : $this->cookieDomain)) === '' ? '' : '; domain=' . $domain)
262-
. (($secure ?? $this->cookieSecure) ? '; secure' : '')
266+
. ($secure ? '; secure' : '')
263267
. (($httpOnly ?? true) ? '; HttpOnly' : '')
264-
. '; SameSite=' . ($sameSite ?? self::SameSiteLax);
268+
. '; SameSite=' . $sameSite;
265269
header('Set-Cookie: ' . $cookie, replace: false);
266270
return $this;
267271
}

tests/Http/Response.setCookie.phpt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,14 @@ Assert::exception(
127127
);
128128

129129

130+
// SameSite=None forces the Secure attribute (otherwise the browser rejects the cookie)
131+
$response = new Http\Response;
132+
$old = headers_list();
133+
$response->setCookie('test', 'value', null, sameSite: Http\IResponse::SameSiteNone);
134+
$headers = array_values(array_diff(headers_list(), $old, ['Set-Cookie:']));
135+
Assert::same(['Set-Cookie: test=value; path=/; secure; HttpOnly; SameSite=None'], $headers);
136+
137+
130138
// integer 0 is deprecated, but kept as a session cookie for BC
131139
$response = new Http\Response;
132140
$old = headers_list();

0 commit comments

Comments
 (0)