Skip to content

Commit

Permalink
[SDK-3635] Enable configuration of SessionStore and CookieStore `same…
Browse files Browse the repository at this point in the history
…site` property (#645)
  • Loading branch information
evansims authored Sep 20, 2022
1 parent ef7f4a3 commit 89b8d8f
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 35 deletions.
5 changes: 5 additions & 0 deletions src/Configuration/SdkConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* @method SdkConfiguration setCookieDomain(?string $cookieDomain = null)
* @method SdkConfiguration setCookieExpires(int $cookieExpires = 0)
* @method SdkConfiguration setCookiePath(string $cookiePath = '/')
* @method SdkConfiguration setCookieSameSite(?string $cookieSameSite)
* @method SdkConfiguration setCookieSecret(?string $cookieSecret)
* @method SdkConfiguration setCookieSecure(bool $cookieSecure = false)
* @method SdkConfiguration setClientId(?string $clientId = null)
Expand Down Expand Up @@ -69,6 +70,7 @@
* @method string|null getCookieDomain(?\Throwable $exceptionIfNull = null)
* @method int getCookieExpires()
* @method string getCookiePath()
* @method string|null getCookieSameSite(?\Throwable $exceptionIfNull = null)
* @method string|null getCookieSecret(?\Throwable $exceptionIfNull = null)
* @method bool getCookieSecure()
* @method string|null getClientId(?\Throwable $exceptionIfNull = null)
Expand Down Expand Up @@ -111,6 +113,7 @@
* @method bool hasCookieDomain()
* @method bool hasCookieExpires()
* @method bool hasCookiePath()
* @method bool hasCookieSameSite()
* @method bool hasCookieSecret()
* @method bool hasCookieSecure()
* @method bool hasClientId()
Expand Down Expand Up @@ -200,6 +203,7 @@ final class SdkConfiguration implements ConfigurableContract
* @param string|null $cookieDomain Defaults to value of HTTP_HOST server environment information. Cookie domain, for example 'www.example.com', for use with PHP sessions and SDK cookies. To make cookies visible on all subdomains then the domain must be prefixed with a dot like '.example.com'.
* @param int $cookieExpires Defaults to 0. How long, in seconds, before cookies expire. If set to 0 the cookie will expire at the end of the session (when the browser closes).
* @param string $cookiePath Defaults to '/'. Specifies path on the domain where the cookies will work. Use a single slash ('/') for all paths on the domain.
* @param string|null $cookieSameSite Defaults to 'lax'. Specifies whether cookies should be restricted to first-party or same-site context.
* @param bool $cookieSecure Defaults to false. Specifies whether cookies should ONLY be sent over secure connections.
* @param bool $persistUser Defaults to true. If true, the user data will persist in session storage.
* @param bool $persistIdToken Defaults to true. If true, the Id Token will persist in session storage.
Expand Down Expand Up @@ -247,6 +251,7 @@ public function __construct(
int $cookieExpires = 0,
string $cookiePath = '/',
bool $cookieSecure = false,
?string $cookieSameSite = null,
bool $persistUser = true,
bool $persistIdToken = true,
bool $persistAccessToken = true,
Expand Down
75 changes: 41 additions & 34 deletions src/Store/CookieStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ public function setState(): self
// Push the updated cookie to the host device for persistence.
// @codeCoverageIgnoreStart
if (! defined('AUTH0_TESTS_DIR')) {
/** @var array{expires?: int, path?: string, domain?: string, secure?: bool, httponly?: bool, samesite?: 'Lax'|'lax'|'None'|'none'|'Strict'|'strict', url_encode?: int} $setOptions */
setcookie($cookieName, $chunk, $setOptions);
}
// @codeCoverageIgnoreEnd
Expand All @@ -231,6 +232,7 @@ public function setState(): self
// Push the cookie deletion command to the host device.
// @codeCoverageIgnoreStart
if (! defined('AUTH0_TESTS_DIR')) {
/** @var array{expires?: int, path?: string, domain?: string, secure?: bool, httponly?: bool, samesite?: 'Lax'|'lax'|'None'|'none'|'Strict'|'strict', url_encode?: int} $deleteOptions */
setcookie($cookieName, '', $deleteOptions);
}
// @codeCoverageIgnoreEnd
Expand Down Expand Up @@ -320,6 +322,45 @@ public function purge(): void
}
}


/**
* Build options array for use with setcookie()
*
* @param int|null $expires
*
* @return array{expires: int, path: string, domain?: string, secure: bool, httponly: bool, samesite: string, url_encode?: int}
*/
public function getCookieOptions(
?int $expires = null
): array {
$expires = $expires ?? $this->configuration->getCookieExpires();

if ($expires !== 0) {
$expires = time() + $expires;
}

$options = [
'expires' => $expires,
'path' => $this->configuration->getCookiePath(),
'secure' => $this->configuration->getCookieSecure(),
'httponly' => true,
'samesite' => $this->configuration->getResponseMode() === 'form_post' ? 'None' : $this->configuration->getCookieSameSite() ?? 'Lax'
];

if (! in_array(strtolower($options['samesite']), ['lax', 'none', 'strict'], true)) {
$options['samesite'] = 'Lax';
}

$domain = $this->configuration->getCookieDomain() ?? $_SERVER['HTTP_HOST'] ?? null;

if ($domain !== null) {
/** @var string $domain */
$options['domain'] = $domain;
}

return $options;
}

/**
* Encrypt data for safe storage format for a cookie.
*
Expand Down Expand Up @@ -443,38 +484,4 @@ private function decrypt(
/** @var array<mixed> $data */
return $data;
}

/**
* Build options array for use with setcookie()
*
* @param int|null $expires
*
* @return array{expires?: int, path?: string, domain?: string, secure?: bool, httponly?: bool, samesite?: 'Lax'|'lax'|'None'|'none'|'Strict'|'strict', url_encode?: int}
*/
private function getCookieOptions(
?int $expires = null
): array {
$expires = $expires ?? $this->configuration->getCookieExpires();

if ($expires !== 0) {
$expires = time() + $expires;
}

$options = [
'expires' => $expires,
'path' => $this->configuration->getCookiePath(),
'secure' => $this->configuration->getCookieSecure(),
'httponly' => true,
'samesite' => $this->configuration->getResponseMode() === 'form_post' ? 'None' : 'Lax',
];

$domain = $this->configuration->getCookieDomain() ?? $_SERVER['HTTP_HOST'] ?? null;

if ($domain !== null) {
/** @var string $domain */
$options['domain'] = $domain;
}

return $options;
}
}
2 changes: 1 addition & 1 deletion src/Store/SessionStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ private function start(): void
'path' => $this->configuration->getCookiePath(),
'secure' => $this->configuration->getCookieSecure(),
'httponly' => true,
'samesite' => $this->configuration->getResponseMode() === 'form_post' ? 'None' : 'Lax',
'samesite' => $this->configuration->getResponseMode() === 'form_post' ? 'None' : $this->configuration->getCookieSameSite() ?? 'Lax',
]);
}
// @codeCoverageIgnoreEnd
Expand Down
16 changes: 16 additions & 0 deletions tests/Unit/Store/CookieStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,19 @@

expect($this->store->getState())->toBeEmpty();
});

test('Configured SameSite() is reflected', function(): void {
$this->configuration->setCookieSameSite('strict');

$options = $this->store->getCookieOptions();

expect($options['samesite'])->toEqual('strict');
});

test('Unsupported configured SameSite() is overwritten by default of `lax`', function(): void {
$this->configuration->setCookieSameSite('testing');

$options = $this->store->getCookieOptions();

expect($options['samesite'])->toEqual('Lax');
});

0 comments on commit 89b8d8f

Please sign in to comment.