Skip to content

NEW Allow setting "samesite" attribute for individual cookies.#11632

Merged
emteknetnz merged 1 commit intosilverstripe:6.0from
creative-commoners:pulls/6.0/cookie-samesite
Mar 13, 2025
Merged

NEW Allow setting "samesite" attribute for individual cookies.#11632
emteknetnz merged 1 commit intosilverstripe:6.0from
creative-commoners:pulls/6.0/cookie-samesite

Conversation

@GuySartorelli
Copy link
Copy Markdown
Member

Also update secure-ness of authentication cookies to match session cookies, and finish securing session cookies.

Issue

Comment on lines +30 to +29
private bool $tokenCookieSecure = true;

/**
* @var boolean
* The SameSite value to use for authentication cookies.
* If set to an empty string, the default value from Cookie.default_samesite will be used.
*/
private $tokenCookieSecure = false;
private string $tokenCookieSameSite = Cookie::SAMESITE_STRICT;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In https://docs.silverstripe.org/en/6/developer_guides/security/secure_coding/#secure-sessions-and-cookies when talking about updating the session cookie security it says

The same treatment should be applied to the cookie responsible for remembering logins across sessions

For this reason I've chosen to do exactly that - I've updated its default secure and samesite values to match the session cookie values.

Comment on lines 89 to 79
*/
public function getTokenCookieSecure()
public function getTokenCookieSecure(): bool
{
if ($this->getTokenCookieSameSite() === Cookie::SAMESITE_NONE) {
return true;
}
return $this->tokenCookieSecure;
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matches how "secure" is set for session cookies

Comment thread src/Control/Cookie.php
Comment on lines 17 to 21

public const SAMESITE_LAX = 'Lax';

public const SAMESITE_STRICT = 'Strict';

public const SAMESITE_LAX = 'Lax';

public const SAMESITE_NONE = 'None';
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the order so it's most to least secure

Comment thread src/Control/Cookie.php
* @var bool
*/
private static $report_errors = true;
private static bool $report_errors = true;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A fair amount of unrelated strict typing being added in this PR

Comment thread src/Control/Cookie.php
Comment on lines +48 to +55
string $name,
string|false $value,
int $expiry = 90,
?string $path = null,
?string $domain = null,
bool $secure = false,
bool $httpOnly = true,
string $sameSite = ''
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • $value can only be a string or false if you trace through the calls in the actual implementation.
  • $expiry can only be int.
  • Chose to keep the nullability of $path and $domain to reduce the change of unexpected side effects and regressions given we're so close to the beta.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These types are shared with CookieJar and Cookie_Backend as well.

Comment thread src/Control/Cookie.php
Comment on lines +83 to 92
public static function force_expiry(
string $name,
?string $path = null,
?string $domain = null,
bool $secure = false,
bool $httpOnly = true,
string $sameSite = ''
): void {
Cookie::get_inst()->forceExpiry($name, $path, $domain, $secure, $httpOnly, $sameSite);
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to https://www.php.net/manual/en/function.setcookie.php#refsect1-function.setcookie-notes

Cookies must be deleted with the same parameters as they were set with.

So it seems like we need to allow setting samesite here.

Comment thread src/Control/CookieJar.php
*
* @param array $cookies The existing cookies to load into the cookie jar.
* Omit this to default to $_COOKIE
* @inheritDoc
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed a lot of duplicate PHPDoc from this class

Comment thread src/Control/Session.php Outdated
Comment on lines +375 to +376
$sameSite = static::config()->get('cookie_samesite') ?? Cookie::SAMESITE_LAX;
$sameSite = static::config()->get('cookie_samesite') ?? Cookie::SAMESITE_STRICT;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and any other changes from lax to strict in this class are necessary as part of #11597

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should move this logic to a new method Session::getCookieSamesite()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only used in one place so that seems excessive - but done anyway. Private method so we can just axe it later if we want.

Comment thread src/Control/Session.php
Comment on lines -413 to +422
$path = $this->config()->get('cookie_path') ?: Director::baseURL();
$domain = $this->config()->get('cookie_domain');
$secure = Director::is_https($request) && $this->config()->get('cookie_secure');
Cookie::force_expiry(session_name(), $path, $domain, $secure, true);
$cookieParams = $this->buildCookieParams($request);
Cookie::force_expiry(
session_name(),
$cookieParams['path'],
$cookieParams['domain'],
$cookieParams['secure'],
true,
$cookieParams['samesite']
);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No sense rebuilding the params - updated this to use buildCookieParams()

Comment thread src/Security/Member.php
* @config
* @var String If this is set, then a session cookie with the given name will be set on log-in,
* and cleared on logout.
* This cookie shares the same SameSite and Secure parameters as the main session cookie.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See SessionAuthenticationHandler

@GuySartorelli
Copy link
Copy Markdown
Member Author

Based on the existing tests it doesn't look like there's a sensible way to test this new parameter is actually coming through when cookies get set.

@GuySartorelli GuySartorelli force-pushed the pulls/6.0/cookie-samesite branch 2 times, most recently from bc01ee8 to 9ad275d Compare March 11, 2025 01:02
Comment thread src/Control/CookieJar.php
* @param string $path The path to save the cookie on (falls back to site base)
* @param string $domain The domain to make the cookie available on
* @param boolean $secure Can the cookie only be sent over SSL?
* @param boolean $httpOnly Prevent the cookie being accessible by JS
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be deleting docblock params when they're explaining something - e.g. $expiry and $path are non obvious.

This applies other instances of deleting docblock params in this PR

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note the @inheritdoc immediately below the text you selected. These are duplicate PHPDocs as mentioned in #11632 (comment)

Comment thread src/Control/CookieJar.php Outdated
string $sameSite = ''
): void {
if ($sameSite === '') {
$sameSite = Cookie::config()->get('default_samesite') ?? Cookie::SAMESITE_LAX;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should logic should be on a new method Cookie::getDefaultSameSite()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/Control/CookieJar.php Outdated
string $sameSite = ''
): bool {
if ($sameSite === '') {
$sameSite = Cookie::config()->get('default_samesite') ?? Cookie::SAMESITE_LAX;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should logic should be on a new method Cookie::getDefaultSameSite()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/Control/Session.php Outdated
Comment on lines +375 to +376
$sameSite = static::config()->get('cookie_samesite') ?? Cookie::SAMESITE_LAX;
$sameSite = static::config()->get('cookie_samesite') ?? Cookie::SAMESITE_STRICT;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should move this logic to a new method Session::getCookieSamesite()

public function getDeviceCookieName(): string
{
return $this->deviceCookieName;
return $this->deviceCookieName ?? '';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return $this->deviceCookieName ?? '';
return $this->deviceCookieName;

Won't PHP complain about trying to access an un-initilised value?

Seems like it would be better to just give the property a default value of blank string

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it only does that for properties which are explicitly typed. But I guess I can make it explicitly string.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

public function getTokenCookieName(): string
{
return $this->tokenCookieName;
return $this->tokenCookieName ?? '';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return $this->tokenCookieName ?? '';
return $this->tokenCookieName;

Same as getDeviceCookieName() above

Comment thread src/Control/Cookie_Backend.php Outdated
Comment on lines +28 to +29
* @param ?string $path The path to save the cookie on (falls back to site base)
* @param ?string $domain The domain to make the cookie available on
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param ?string $path The path to save the cookie on (falls back to site base)
* @param ?string $domain The domain to make the cookie available on
* @param null|string $path The path to save the cookie on (falls back to site base)
* @param null|string $domain The domain to make the cookie available on

Don't think I've seen ? in a docblock param before, should be consistent

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@GuySartorelli GuySartorelli force-pushed the pulls/6.0/cookie-samesite branch from 9ad275d to f200906 Compare March 12, 2025 03:57
@GuySartorelli
Copy link
Copy Markdown
Member Author

CI failures are pre-existing and will be resolved in #11635

Also update secure-ness of authentication cookies to match session
cookies, and finish securing session cookies.
@GuySartorelli GuySartorelli force-pushed the pulls/6.0/cookie-samesite branch from f200906 to 33a6a13 Compare March 12, 2025 22:33
@emteknetnz emteknetnz merged commit a8e6cb4 into silverstripe:6.0 Mar 13, 2025
9 of 12 checks passed
@emteknetnz emteknetnz deleted the pulls/6.0/cookie-samesite branch March 13, 2025 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants