diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 7e28614a..e90e9ddf 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -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() diff --git a/DependencyInjection/LexikJWTAuthenticationExtension.php b/DependencyInjection/LexikJWTAuthenticationExtension.php index 07a4b0d4..a91b5712 100644 --- a/DependencyInjection/LexikJWTAuthenticationExtension.php +++ b/DependencyInjection/LexikJWTAuthenticationExtension.php @@ -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. @@ -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) @@ -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); } diff --git a/Resources/config/cookie.xml b/Resources/config/cookie.xml index e47ea10d..e666c700 100644 --- a/Resources/config/cookie.xml +++ b/Resources/config/cookie.xml @@ -14,6 +14,7 @@ null + false diff --git a/Resources/doc/1-configuration-reference.rst b/Resources/doc/1-configuration-reference.rst index 43bf8a90..1ae2be45 100644 --- a/Resources/doc/1-configuration-reference.rst +++ b/Resources/doc/1-configuration-reference.rst @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -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 @@ -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 diff --git a/Security/Http/Cookie/JWTCookieProvider.php b/Security/Http/Cookie/JWTCookieProvider.php index 54dbed6b..460af6e7 100644 --- a/Security/Http/Cookie/JWTCookieProvider.php +++ b/Security/Http/Cookie/JWTCookieProvider.php @@ -4,6 +4,7 @@ use Lexik\Bundle\JWTAuthenticationBundle\Helper\JWTSplitter; use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpKernel\Kernel; /** * Creates secure JWT cookies. @@ -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; @@ -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)); + } } /** @@ -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__)); @@ -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); @@ -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, ); } }