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

feat: add support for partitioned cookies #1167

Merged
merged 1 commit into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->scalarNode('domain')->defaultNull()->end()
->scalarNode('secure')->defaultTrue()->end()
->scalarNode('httpOnly')->defaultTrue()->end()
->scalarNode('partitioned')->defaultFalse()->end()
->arrayNode('split')
->scalarPrototype()->end()
->end()
Expand Down
8 changes: 7 additions & 1 deletion DependencyInjection/LexikJWTAuthenticationExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\HttpKernel\Kernel;

/**
* This is the class that loads and manages your bundle configuration.
Expand Down Expand Up @@ -115,6 +116,10 @@ public function load(array $configs, ContainerBuilder $container): void

$cookieProviders = [];
foreach ($config['set_cookies'] as $name => $attributes) {
if ($attributes['partitioned'] && Kernel::VERSION < '6.4') {
throw new \LogicException(sprintf('The `partitioned` option for cookies is only available for Symfony 6.4 and above. You are currently on version %s', Kernel::VERSION));
}

$container
->setDefinition($id = "lexik_jwt_authentication.cookie_provider.$name", new ChildDefinition('lexik_jwt_authentication.cookie_provider'))
->replaceArgument(0, $name)
Expand All @@ -124,7 +129,8 @@ public function load(array $configs, ContainerBuilder $container): void
->replaceArgument(4, $attributes['domain'])
->replaceArgument(5, $attributes['secure'])
->replaceArgument(6, $attributes['httpOnly'])
->replaceArgument(7, $attributes['split']);
->replaceArgument(7, $attributes['split'])
->replaceArgument(8, $attributes['partitioned']);
$cookieProviders[] = new Reference($id);
}

Expand Down
1 change: 1 addition & 0 deletions Resources/config/cookie.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<argument/> <!-- Default secure -->
<argument/> <!-- Default httpOnly -->
<argument>null</argument> <!-- Default split -->
<argument>false</argument> <!-- Default partitioned -->
</service>
</services>
</container>
3 changes: 3 additions & 0 deletions Resources/doc/1-configuration-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ when the cookie token extractor is enabled
# domain: null (null means automatically set by symfony)
# secure: true (default to true)
# httpOnly: true
# partitioned: false

Automatically generating split cookies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -170,6 +171,7 @@ Keep in mind, that SameSite attribute is **not supported** in
path: /
domain: null
httpOnly: false
partitioned: false # Only for Symfony 6.4 or higher
split:
- header
- payload
Expand All @@ -180,6 +182,7 @@ Keep in mind, that SameSite attribute is **not supported** in
path: /
domain: null
httpOnly: true
partitioned: false # Only for Symfony 6.4 or higher
split:
- signature

Expand Down
18 changes: 15 additions & 3 deletions Security/Http/Cookie/JWTCookieProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Lexik\Bundle\JWTAuthenticationBundle\Helper\JWTSplitter;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpKernel\Kernel;

/**
* Creates secure JWT cookies.
Expand All @@ -18,8 +19,9 @@ final class JWTCookieProvider
private $defaultSecure;
private $defaultHttpOnly;
private $defaultSplit;
private $defaultPartitioned;

public function __construct(?string $defaultName = null, ?int $defaultLifetime = 0, ?string $defaultSameSite = Cookie::SAMESITE_LAX, ?string $defaultPath = '/', ?string $defaultDomain = null, bool $defaultSecure = true, bool $defaultHttpOnly = true, array $defaultSplit = [])
public function __construct(?string $defaultName = null, ?int $defaultLifetime = 0, ?string $defaultSameSite = Cookie::SAMESITE_LAX, ?string $defaultPath = '/', ?string $defaultDomain = null, bool $defaultSecure = true, bool $defaultHttpOnly = true, array $defaultSplit = [], bool $defaultPartitioned = false)
{
$this->defaultName = $defaultName;
$this->defaultLifetime = $defaultLifetime;
Expand All @@ -29,6 +31,11 @@ public function __construct(?string $defaultName = null, ?int $defaultLifetime =
$this->defaultSecure = $defaultSecure;
$this->defaultHttpOnly = $defaultHttpOnly;
$this->defaultSplit = $defaultSplit;
$this->defaultPartitioned = $defaultPartitioned;

if ($defaultPartitioned && Kernel::VERSION < '6.4') {
throw new \LogicException(sprintf('The `partitioned` option for cookies is only available for Symfony 6.4 and above. You are currently on version %s', Kernel::VERSION));
}
}

/**
Expand All @@ -37,7 +44,7 @@ public function __construct(?string $defaultName = null, ?int $defaultLifetime =
* For each argument (all args except $jwt), if omitted or set to null then the
* default value defined via the constructor will be used.
*/
public function createCookie(string $jwt, ?string $name = null, $expiresAt = null, ?string $sameSite = null, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, array $split = []): Cookie
public function createCookie(string $jwt, ?string $name = null, $expiresAt = null, ?string $sameSite = null, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, array $split = [], ?bool $partitioned = null): Cookie
{
if (!$name && !$this->defaultName) {
throw new \LogicException(sprintf('The cookie name must be provided, either pass it as 2nd argument of %s or set a default name via the constructor.', __METHOD__));
Expand All @@ -47,6 +54,10 @@ public function createCookie(string $jwt, ?string $name = null, $expiresAt = nul
throw new \LogicException(sprintf('The cookie expiration time must be provided, either pass it as 3rd argument of %s or set a default lifetime via the constructor.', __METHOD__));
}

if ($partitioned && Kernel::VERSION < '6.4') {
throw new \LogicException(sprintf('The `partitioned` option for cookies is only available for Symfony 6.4 and above. You are currently on version %s', Kernel::VERSION));
}

$jwtParts = new JWTSplitter($jwt);
$jwt = $jwtParts->getParts($split ?: $this->defaultSplit);

Expand All @@ -63,7 +74,8 @@ public function createCookie(string $jwt, ?string $name = null, $expiresAt = nul
$secure ?: $this->defaultSecure,
$httpOnly ?: $this->defaultHttpOnly,
false,
$sameSite ?: $this->defaultSameSite
$sameSite ?: $this->defaultSameSite,
$partitioned ?: $this->defaultPartitioned,
);
}
}
Loading