Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 1 commit into
base: 6.0
Choose a base branch
from

Conversation

GuySartorelli
Copy link
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
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
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 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
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

/**
* @config
*
* @var bool
*/
private static $report_errors = true;
private static bool $report_errors = true;
Copy link
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 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
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
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 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
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.

* When creating the backend we want to store the existing cookies in our
* "existing" array. This allows us to distinguish between cookies we received
* or we set ourselves (and didn't get from the browser)
*
* @param array $cookies The existing cookies to load into the cookie jar.
* Omit this to default to $_COOKIE
* @inheritDoc
Copy link
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 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
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
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
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 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
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()

@@ -220,6 +220,7 @@ class Member extends DataObject
* @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
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
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
* @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
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
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)

string $sameSite = ''
): void {
if ($sameSite === '') {
$sameSite = Cookie::config()->get('default_samesite') ?? Cookie::SAMESITE_LAX;
Copy link
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
Member Author

Choose a reason for hiding this comment

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

Done

string $sameSite = ''
): bool {
if ($sameSite === '') {
$sameSite = Cookie::config()->get('default_samesite') ?? Cookie::SAMESITE_LAX;
Copy link
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
Member Author

Choose a reason for hiding this comment

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

Done

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
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()

{
return $this->deviceCookieName;
return $this->deviceCookieName ?? '';
Copy link
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
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
Member Author

Choose a reason for hiding this comment

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

Done

{
return $this->tokenCookieName;
return $this->tokenCookieName ?? '';
Copy link
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 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
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
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
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
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