Skip to content

Commit

Permalink
feature #1167 feat: add support for partitioned cookies (EmilePerron)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the 2.x branch.

Discussion
----------

feat: add support for partitioned cookies

This PR adds support for partitioned third-party cookies in a backwards compatible way, as described in #1166.

I have updated the documentation's configuration examples, but have not added any tests regarding this new option as the existing tests did not seem to cover similar options. I'll gladly add some if you think it's worth it.

Let me know if you have any comments/feedback!

---

closes #1166

Commits
-------

2d1d058 feat: add support for partitioned cookies
  • Loading branch information
chalasr committed Nov 30, 2023
2 parents 3b02f52 + 2d1d058 commit 5c9bcac
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 5c9bcac

Please sign in to comment.