From 9dd12819d112beaf069bebd7a927236ed750caef Mon Sep 17 00:00:00 2001 From: Andrej Hudec Date: Fri, 20 Jun 2025 14:46:44 +0200 Subject: [PATCH 1/9] Add AuthTargetUrlEvent --- .../GrantAccessByOAuth2TokenProcess.php | 12 +++-- src/Event/AuthTargetUrlEvent.php | 50 +++++++++++++++++++ src/Util/HttpUtil.php | 2 +- 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 src/Event/AuthTargetUrlEvent.php diff --git a/src/Domain/Process/OAuth2/GrantAccessByOAuth2TokenProcess.php b/src/Domain/Process/OAuth2/GrantAccessByOAuth2TokenProcess.php index 9349285..43ce5d1 100644 --- a/src/Domain/Process/OAuth2/GrantAccessByOAuth2TokenProcess.php +++ b/src/Domain/Process/OAuth2/GrantAccessByOAuth2TokenProcess.php @@ -7,6 +7,7 @@ use AnzuSystems\AuthBundle\Contracts\AnzuAuthUserInterface; use AnzuSystems\AuthBundle\Contracts\OAuth2AuthUserRepositoryInterface; use AnzuSystems\AuthBundle\Domain\Process\GrantAccessOnResponseProcess; +use AnzuSystems\AuthBundle\Event\AuthTargetUrlEvent; use AnzuSystems\AuthBundle\Exception\InvalidJwtException; use AnzuSystems\AuthBundle\Exception\UnsuccessfulAccessTokenRequestException; use AnzuSystems\AuthBundle\Exception\UnsuccessfulUserInfoRequestException; @@ -21,6 +22,7 @@ use Exception; use Lcobucci\JWT\Token\RegisteredClaims; use Psr\Log\LoggerInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -44,6 +46,7 @@ public function __construct( private readonly LoggerInterface $appLogger, private readonly LogContextFactory $contextFactory, private readonly string $authMethod, + private readonly EventDispatcherInterface $eventDispatcher, ) { } @@ -91,7 +94,7 @@ public function execute(Request $request): Response } try { - $response = $this->createRedirectResponseForRequest($request, UserOAuthLoginState::Success); + $response = $this->createRedirectResponseForRequest($request, UserOAuthLoginState::Success, $authUser); return $this->grantAccessOnResponseProcess->execute($authUser->getAuthId(), $request, $response); } catch (Exception $exception) { @@ -101,7 +104,7 @@ public function execute(Request $request): Response } } - public function createRedirectResponseForRequest(Request $request, UserOAuthLoginState $loginState): RedirectResponse + public function createRedirectResponseForRequest(Request $request, UserOAuthLoginState $loginState, ?AnzuAuthUserInterface $authUser = null): RedirectResponse { $redirectUrl = $this->httpUtil->getAuthRedirectUrlFromRequest($request); $redirectUrl .= '?'; @@ -110,7 +113,10 @@ public function createRedirectResponseForRequest(Request $request, UserOAuthLogi self::TIMESTAMP_QUERY_PARAM => time(), ]); - return new RedirectResponse($redirectUrl); + $event = new AuthTargetUrlEvent($redirectUrl, $request, $loginState, $authUser); + $this->eventDispatcher->dispatch($event, AuthTargetUrlEvent::NAME); + + return new RedirectResponse($event->getTargetUrl()); } /** diff --git a/src/Event/AuthTargetUrlEvent.php b/src/Event/AuthTargetUrlEvent.php new file mode 100644 index 0000000..221d0f5 --- /dev/null +++ b/src/Event/AuthTargetUrlEvent.php @@ -0,0 +1,50 @@ +targetUrl; + } + + public function setTargetUrl(string $targetUrl): self + { + $this->targetUrl = $targetUrl; + + return $this; + } + + public function getRequest(): Request + { + return $this->request; + } + + public function getLoginState(): UserOAuthLoginState + { + return $this->loginState; + } + + public function getAuthUser(): ?AnzuAuthUserInterface + { + return $this->authUser; + } +} diff --git a/src/Util/HttpUtil.php b/src/Util/HttpUtil.php index 112ae9a..d8db8ae 100644 --- a/src/Util/HttpUtil.php +++ b/src/Util/HttpUtil.php @@ -190,7 +190,7 @@ private function createCookie( $this->cookieConfiguration->isSecure(), $httpOnly, false, - Cookie::SAMESITE_STRICT + Cookie::SAMESITE_NONE ); } From 4b7a10b4185cf4b3fe7ae6983117a93a73244125 Mon Sep 17 00:00:00 2001 From: Andrej Hudec Date: Sat, 21 Jun 2025 11:43:20 +0200 Subject: [PATCH 2/9] Add prefix to event --- src/Event/AuthTargetUrlEvent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Event/AuthTargetUrlEvent.php b/src/Event/AuthTargetUrlEvent.php index 221d0f5..7ed5f41 100644 --- a/src/Event/AuthTargetUrlEvent.php +++ b/src/Event/AuthTargetUrlEvent.php @@ -11,7 +11,7 @@ final class AuthTargetUrlEvent extends Event { - public const string NAME = 'auth.targetUrl'; + public const string NAME = 'anzu_auth.targetUrl'; public function __construct( private string $targetUrl, From 38fbb43e37e2d5784f6985f9e13e1f0e73272539 Mon Sep 17 00:00:00 2001 From: Andrej Hudec Date: Sat, 21 Jun 2025 17:56:08 +0200 Subject: [PATCH 3/9] Change event name --- src/Event/AuthTargetUrlEvent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Event/AuthTargetUrlEvent.php b/src/Event/AuthTargetUrlEvent.php index 7ed5f41..ea08693 100644 --- a/src/Event/AuthTargetUrlEvent.php +++ b/src/Event/AuthTargetUrlEvent.php @@ -11,7 +11,7 @@ final class AuthTargetUrlEvent extends Event { - public const string NAME = 'anzu_auth.targetUrl'; + public const string NAME = 'anzu_auth.target_url'; public function __construct( private string $targetUrl, From fca33bc38d9f8e225df04ff9db30f452c12a0763 Mon Sep 17 00:00:00 2001 From: Andrej Hudec Date: Sun, 22 Jun 2025 08:08:55 +0200 Subject: [PATCH 4/9] Add cookie samesite configuration option --- src/Configuration/CookieConfiguration.php | 6 ++++++ src/DependencyInjection/AnzuSystemsAuthExtension.php | 1 + src/DependencyInjection/Configuration.php | 6 ++++++ src/Resources/config/services.php | 1 + tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php | 2 ++ tests/Util/HttpUtilTest.php | 1 + 6 files changed, 17 insertions(+) diff --git a/src/Configuration/CookieConfiguration.php b/src/Configuration/CookieConfiguration.php index 4fc906a..49a0c0a 100644 --- a/src/Configuration/CookieConfiguration.php +++ b/src/Configuration/CookieConfiguration.php @@ -9,6 +9,7 @@ final class CookieConfiguration public function __construct( private readonly ?string $domain, private readonly bool $secure, + private readonly ?string $sameSite, private readonly string $jwtPayloadCookieName, private readonly string $jwtSignatureCookieName, private readonly string $deviceIdCookieName, @@ -23,6 +24,11 @@ public function getDomain(): ?string return $this->domain; } + public function getSameSite(): ?string + { + return $this->sameSite; + } + public function isSecure(): bool { return $this->secure; diff --git a/src/DependencyInjection/AnzuSystemsAuthExtension.php b/src/DependencyInjection/AnzuSystemsAuthExtension.php index 5bd8bca..d4f6c9d 100644 --- a/src/DependencyInjection/AnzuSystemsAuthExtension.php +++ b/src/DependencyInjection/AnzuSystemsAuthExtension.php @@ -38,6 +38,7 @@ public function load(array $configs, ContainerBuilder $container): void $cookieSection = $processedConfig['cookie']; $container->setParameter('anzu_systems.auth_bundle.cookie.domain', $cookieSection['domain']); + $container->setParameter('anzu_systems.auth_bundle.cookie.samesite', $cookieSection['samesite']); $container->setParameter('anzu_systems.auth_bundle.cookie.secure', $cookieSection['secure']); $container->setParameter('anzu_systems.auth_bundle.cookie.device_id_name', $cookieSection['device_id_name']); $container->setParameter('anzu_systems.auth_bundle.cookie.jwt.payload_part_name', $cookieSection['jwt']['payload_part_name']); diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 509d535..01ef901 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -13,6 +13,7 @@ use Symfony\Component\Config\Definition\Builder\NodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\HttpFoundation\Cookie; final class Configuration implements ConfigurationInterface { @@ -41,6 +42,11 @@ private function addCookieSection(): NodeDefinition ->addDefaultsIfNotSet() ->children() ->scalarNode('domain')->defaultValue(null)->end() + ->enumNode('samesite') + ->values([Cookie::SAMESITE_NONE, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT]) + ->defaultValue(Cookie::SAMESITE_STRICT) + ->info('SameSite attribute for cookies (lax, strict or none)') + ->end() ->booleanNode('secure')->isRequired()->end() ->scalarNode('device_id_name')->defaultValue('anz_di')->end() ->arrayNode('jwt') diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index 9a68137..79129f9 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -18,6 +18,7 @@ $services ->set(CookieConfiguration::class) ->arg('$domain', param('anzu_systems.auth_bundle.cookie.domain')) + ->arg('$sameSite', param('anzu_systems.auth_bundle.cookie.samesite')) ->arg('$secure', param('anzu_systems.auth_bundle.cookie.secure')) ->arg('$deviceIdCookieName', param('anzu_systems.auth_bundle.cookie.device_id_name')) ->arg('$jwtPayloadCookieName', param('anzu_systems.auth_bundle.cookie.jwt.payload_part_name')) diff --git a/tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php b/tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php index 9bab4c7..5a6f824 100644 --- a/tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php +++ b/tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php @@ -66,6 +66,7 @@ public function testFullConfiguration(): void $loader->load([$config], $this->configuration); $this->assertParameter('.example.com', 'anzu_systems.auth_bundle.cookie.domain'); + $this->assertParameter('lax', 'anzu_systems.auth_bundle.cookie.samesite'); $this->assertParameter(true, 'anzu_systems.auth_bundle.cookie.secure'); $this->assertParameter('anz_di', 'anzu_systems.auth_bundle.cookie.device_id_name'); $this->assertParameter('anz_jp', 'anzu_systems.auth_bundle.cookie.jwt.payload_part_name'); @@ -129,6 +130,7 @@ private function getFullConfig(): array $yaml = << Date: Sun, 22 Jun 2025 08:14:21 +0200 Subject: [PATCH 5/9] Rename samesite to same_site --- src/DependencyInjection/AnzuSystemsAuthExtension.php | 2 +- src/DependencyInjection/Configuration.php | 2 +- src/Resources/config/services.php | 2 +- tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/DependencyInjection/AnzuSystemsAuthExtension.php b/src/DependencyInjection/AnzuSystemsAuthExtension.php index d4f6c9d..cb3dde9 100644 --- a/src/DependencyInjection/AnzuSystemsAuthExtension.php +++ b/src/DependencyInjection/AnzuSystemsAuthExtension.php @@ -38,7 +38,7 @@ public function load(array $configs, ContainerBuilder $container): void $cookieSection = $processedConfig['cookie']; $container->setParameter('anzu_systems.auth_bundle.cookie.domain', $cookieSection['domain']); - $container->setParameter('anzu_systems.auth_bundle.cookie.samesite', $cookieSection['samesite']); + $container->setParameter('anzu_systems.auth_bundle.cookie.same_site', $cookieSection['same_site']); $container->setParameter('anzu_systems.auth_bundle.cookie.secure', $cookieSection['secure']); $container->setParameter('anzu_systems.auth_bundle.cookie.device_id_name', $cookieSection['device_id_name']); $container->setParameter('anzu_systems.auth_bundle.cookie.jwt.payload_part_name', $cookieSection['jwt']['payload_part_name']); diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 01ef901..78bd09c 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -42,7 +42,7 @@ private function addCookieSection(): NodeDefinition ->addDefaultsIfNotSet() ->children() ->scalarNode('domain')->defaultValue(null)->end() - ->enumNode('samesite') + ->enumNode('same_site') ->values([Cookie::SAMESITE_NONE, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT]) ->defaultValue(Cookie::SAMESITE_STRICT) ->info('SameSite attribute for cookies (lax, strict or none)') diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index 79129f9..46d935d 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -18,7 +18,7 @@ $services ->set(CookieConfiguration::class) ->arg('$domain', param('anzu_systems.auth_bundle.cookie.domain')) - ->arg('$sameSite', param('anzu_systems.auth_bundle.cookie.samesite')) + ->arg('$sameSite', param('anzu_systems.auth_bundle.cookie.same_site')) ->arg('$secure', param('anzu_systems.auth_bundle.cookie.secure')) ->arg('$deviceIdCookieName', param('anzu_systems.auth_bundle.cookie.device_id_name')) ->arg('$jwtPayloadCookieName', param('anzu_systems.auth_bundle.cookie.jwt.payload_part_name')) diff --git a/tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php b/tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php index 5a6f824..8bf7fd4 100644 --- a/tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php +++ b/tests/DependencyInjection/AnzuSystemsAuthExtensionTest.php @@ -66,7 +66,7 @@ public function testFullConfiguration(): void $loader->load([$config], $this->configuration); $this->assertParameter('.example.com', 'anzu_systems.auth_bundle.cookie.domain'); - $this->assertParameter('lax', 'anzu_systems.auth_bundle.cookie.samesite'); + $this->assertParameter('lax', 'anzu_systems.auth_bundle.cookie.same_site'); $this->assertParameter(true, 'anzu_systems.auth_bundle.cookie.secure'); $this->assertParameter('anz_di', 'anzu_systems.auth_bundle.cookie.device_id_name'); $this->assertParameter('anz_jp', 'anzu_systems.auth_bundle.cookie.jwt.payload_part_name'); @@ -130,7 +130,7 @@ private function getFullConfig(): array $yaml = << Date: Sun, 22 Jun 2025 08:20:17 +0200 Subject: [PATCH 6/9] Fix deprecations --- phpunit.xml.dist | 50 +++++++++---------- .../Process/GrantAccessOnResponseProcess.php | 2 +- src/Domain/Process/RefreshTokenProcess.php | 2 +- src/Util/HttpUtil.php | 2 +- src/Util/JwtUtil.php | 2 +- 5 files changed, 27 insertions(+), 31 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 808c625..8b5938f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -5,31 +5,27 @@ backupGlobals="false" colors="true" bootstrap="tests/bootstrap.php" - convertDeprecationsToExceptions="false" -> - - - ./tests - - - - - - - - - - - - - . - - - ./vendor - ./tests - - - - - + cacheDirectory=".phpunit.cache"> + + + ./tests + + + + + + + + + + + + + . + + + ./vendor + ./tests + + diff --git a/src/Domain/Process/GrantAccessOnResponseProcess.php b/src/Domain/Process/GrantAccessOnResponseProcess.php index d408d0f..40283b0 100644 --- a/src/Domain/Process/GrantAccessOnResponseProcess.php +++ b/src/Domain/Process/GrantAccessOnResponseProcess.php @@ -32,7 +32,7 @@ public function __construct( /** * @throws Exception */ - public function execute(string $userId, Request $request, Response $response = null): Response + public function execute(string $userId, Request $request, ?Response $response = null): Response { $jwtExpiresAt = new DateTimeImmutable(sprintf('+%d seconds', $this->jwtConfiguration->getLifetime())); $jwt = $this->jwtUtil->create($userId, $jwtExpiresAt); diff --git a/src/Domain/Process/RefreshTokenProcess.php b/src/Domain/Process/RefreshTokenProcess.php index e19e69b..fb00c89 100644 --- a/src/Domain/Process/RefreshTokenProcess.php +++ b/src/Domain/Process/RefreshTokenProcess.php @@ -24,7 +24,7 @@ public function __construct( /** * @throws Exception */ - public function execute(Request $request, Response $response = null): Response + public function execute(Request $request, ?Response $response = null): Response { $response ??= new JsonResponse(); try { diff --git a/src/Util/HttpUtil.php b/src/Util/HttpUtil.php index d8db8ae..14b789b 100644 --- a/src/Util/HttpUtil.php +++ b/src/Util/HttpUtil.php @@ -91,7 +91,7 @@ public function grabDeviceIdFromRequest(Request $request): string /** * @throws InvalidJwtException */ - public function storeJwtOnResponse(Response $response, Token $token, DateTimeImmutable $expiresAt = null): void + public function storeJwtOnResponse(Response $response, Token $token, ?DateTimeImmutable $expiresAt = null): void { $rawToken = $token->toString(); /** @psalm-suppress PossiblyUndefinedArrayOffset */ diff --git a/src/Util/JwtUtil.php b/src/Util/JwtUtil.php index efc5cff..d494c6c 100644 --- a/src/Util/JwtUtil.php +++ b/src/Util/JwtUtil.php @@ -35,7 +35,7 @@ public function __construct( * * @throws MissingConfigurationException */ - public function create(string $authId, DateTimeImmutable $expiresAt = null, array $claims = []): Plain + public function create(string $authId, ?DateTimeImmutable $expiresAt = null, array $claims = []): Plain { $privateCert = $this->jwtConfiguration->getPrivateCert(); From 7b78e2ac4ccbbb2f0d6f619df99ca8878d51455e Mon Sep 17 00:00:00 2001 From: Andrej Hudec Date: Sun, 22 Jun 2025 08:25:17 +0200 Subject: [PATCH 7/9] use same_site config --- src/Util/HttpUtil.php | 2 +- tests/Util/HttpUtilTest.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Util/HttpUtil.php b/src/Util/HttpUtil.php index 14b789b..61ee79b 100644 --- a/src/Util/HttpUtil.php +++ b/src/Util/HttpUtil.php @@ -190,7 +190,7 @@ private function createCookie( $this->cookieConfiguration->isSecure(), $httpOnly, false, - Cookie::SAMESITE_NONE + $this->cookieConfiguration->getSameSite(), ); } diff --git a/tests/Util/HttpUtilTest.php b/tests/Util/HttpUtilTest.php index 9995ab8..0efa96e 100644 --- a/tests/Util/HttpUtilTest.php +++ b/tests/Util/HttpUtilTest.php @@ -77,10 +77,12 @@ public function testStoreJwtOnResponse(): void $this->assertSame($token->toString(), $payloadCookie->getValue() . '.' . $signCookie->getValue()); $this->assertTrue($payloadCookie->isSecure()); $this->assertFalse($payloadCookie->isHttpOnly()); + $this->assertSame('strict', $payloadCookie->getSameSite()); $this->assertSame('.example.com', $payloadCookie->getDomain()); $this->assertTrue($signCookie->isSecure()); $this->assertTrue($signCookie->isHttpOnly()); + $this->assertSame('strict', $payloadCookie->getSameSite()); $this->assertSame('.example.com', $signCookie->getDomain()); } } From 8e4a7b23c40fa159c03b65192199996b1ae3acc0 Mon Sep 17 00:00:00 2001 From: Andrej Hudec Date: Sun, 22 Jun 2025 08:41:15 +0200 Subject: [PATCH 8/9] Replace enum with scalar as it does not work well with envs --- src/DependencyInjection/Configuration.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 78bd09c..f231240 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -42,10 +42,9 @@ private function addCookieSection(): NodeDefinition ->addDefaultsIfNotSet() ->children() ->scalarNode('domain')->defaultValue(null)->end() - ->enumNode('same_site') - ->values([Cookie::SAMESITE_NONE, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT]) + ->scalarNode('same_site') ->defaultValue(Cookie::SAMESITE_STRICT) - ->info('SameSite attribute for cookies (lax, strict or none)') + ->info('SameSite attribute for cookies (lax, strict, none or null)') ->end() ->booleanNode('secure')->isRequired()->end() ->scalarNode('device_id_name')->defaultValue('anz_di')->end() From cfa802f30618c6e9838ab1fa826208e2264129af Mon Sep 17 00:00:00 2001 From: Andrej Hudec Date: Sun, 22 Jun 2025 09:58:51 +0200 Subject: [PATCH 9/9] Fix psalm issue --- src/Configuration/CookieConfiguration.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Configuration/CookieConfiguration.php b/src/Configuration/CookieConfiguration.php index 49a0c0a..fd27b08 100644 --- a/src/Configuration/CookieConfiguration.php +++ b/src/Configuration/CookieConfiguration.php @@ -4,11 +4,16 @@ namespace AnzuSystems\AuthBundle\Configuration; +use Symfony\Component\HttpFoundation\Cookie; + final class CookieConfiguration { public function __construct( private readonly ?string $domain, private readonly bool $secure, + /** + * @var Cookie::SAMESITE_*|''|null + */ private readonly ?string $sameSite, private readonly string $jwtPayloadCookieName, private readonly string $jwtSignatureCookieName, @@ -24,6 +29,9 @@ public function getDomain(): ?string return $this->domain; } + /** + * @return Cookie::SAMESITE_*|''|null + */ public function getSameSite(): ?string { return $this->sameSite;