Skip to content

Commit

Permalink
feat: add support for partitioned cookies
Browse files Browse the repository at this point in the history
  • Loading branch information
EmilePerron authored and chalasr committed Nov 30, 2023
1 parent 3b02f52 commit 2d1d058
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 4 deletions.
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,
);
}
}

0 comments on commit 2d1d058

Please sign in to comment.