From 6de675ce86fc425d2c74a07bcbdfed910e73f102 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 4 Sep 2025 17:39:25 +0200 Subject: [PATCH 1/5] Set cors allow credentials true --- config/packages/nelmio_cors.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/packages/nelmio_cors.yaml b/config/packages/nelmio_cors.yaml index c766508..7b20162 100644 --- a/config/packages/nelmio_cors.yaml +++ b/config/packages/nelmio_cors.yaml @@ -4,6 +4,7 @@ nelmio_cors: allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] allow_headers: ['Content-Type', 'Authorization'] + allow_credentials: true expose_headers: ['Link'] max_age: 3600 paths: From 06643724f6b62c45be5193cc5ad2d9d3534596d7 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 4 Sep 2025 19:19:59 +0200 Subject: [PATCH 2/5] Set and remove authToken cookie on token post/delete --- .../AuthorizationTokenSubscriber.php | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/EventSubscriber/AuthorizationTokenSubscriber.php diff --git a/src/EventSubscriber/AuthorizationTokenSubscriber.php b/src/EventSubscriber/AuthorizationTokenSubscriber.php new file mode 100644 index 0000000..864e645 --- /dev/null +++ b/src/EventSubscriber/AuthorizationTokenSubscriber.php @@ -0,0 +1,82 @@ + ['onView', EventPriorities::POST_WRITE], + KernelEvents::RESPONSE => ['onResponse', EventPriorities::POST_RESPOND], + ]; + } + + private function getCookieTtl(): \DateTimeInterface + { + return (new \DateTime())->add(new \DateInterval(\sprintf('PT%dS', self::COOKIE_TTL))); + } + + private function getCookie(string $tokenValue) + { + return new Cookie( + name: self::COOKIE_NAME, + value: $tokenValue, + expire: $this->getCookieTtl(), + path: '/', + secure: false, + httpOnly: true, + sameSite: 'Lax' + ); + } + + public function onView(ViewEvent $event): void + { + $resourceClass = $event->getRequest()->attributes->get('_api_resource_class'); + + if ($resourceClass !== UserTokenApiResource::class) { + return; + } + + $event->getRequest()->attributes->set('authTokenMethod', $event->getRequest()->getMethod()); + + if ($event->getRequest()->getMethod() === Request::METHOD_POST) { + $token = $event->getControllerResult(); + + $event->getRequest()->attributes->set('authTokenValue', $token->token); + } + } + + public function onResponse(ResponseEvent $event): void + { + $token = $event->getRequest()->attributes->get('authTokenValue'); + $method = $event->getRequest()->attributes->get('authTokenMethod'); + + if (!$method) { + return; + } + + switch ($method) { + case Request::METHOD_DELETE: + $event->getResponse()->headers->clearCookie(self::COOKIE_NAME); + break; + case Request::METHOD_POST: + $event->getResponse()->headers->setCookie($this->getCookie($token)); + break; + } + } +} From fe247d666f653407afcb44461257e3e742eb153d Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 4 Sep 2025 19:38:07 +0200 Subject: [PATCH 3/5] Centralize cookie consts in AuthServices --- src/EventSubscriber/AuthorizationTokenSubscriber.php | 10 ++++------ src/Service/Auth/AuthService.php | 3 +++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/EventSubscriber/AuthorizationTokenSubscriber.php b/src/EventSubscriber/AuthorizationTokenSubscriber.php index 864e645..742ecdd 100644 --- a/src/EventSubscriber/AuthorizationTokenSubscriber.php +++ b/src/EventSubscriber/AuthorizationTokenSubscriber.php @@ -4,6 +4,7 @@ namespace App\EventSubscriber; use ApiPlatform\Symfony\EventListener\EventPriorities; use App\ApiResource\User\UserTokenApiResource; +use App\Service\Auth\AuthService; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; @@ -13,9 +14,6 @@ use Symfony\Component\HttpKernel\KernelEvents; final class AuthorizationTokenSubscriber implements EventSubscriberInterface { - private const COOKIE_NAME = 'authToken'; - private const COOKIE_TTL = 86400; - public function __construct() {} public static function getSubscribedEvents() @@ -28,13 +26,13 @@ final class AuthorizationTokenSubscriber implements EventSubscriberInterface private function getCookieTtl(): \DateTimeInterface { - return (new \DateTime())->add(new \DateInterval(\sprintf('PT%dS', self::COOKIE_TTL))); + return (new \DateTime())->add(new \DateInterval(\sprintf('PT%dS', AuthService::AUTH_COOKIE_TTL))); } private function getCookie(string $tokenValue) { return new Cookie( - name: self::COOKIE_NAME, + name: AuthService::AUTH_COOKIE_NAME, value: $tokenValue, expire: $this->getCookieTtl(), path: '/', @@ -72,7 +70,7 @@ final class AuthorizationTokenSubscriber implements EventSubscriberInterface switch ($method) { case Request::METHOD_DELETE: - $event->getResponse()->headers->clearCookie(self::COOKIE_NAME); + $event->getResponse()->headers->clearCookie(AuthService::AUTH_COOKIE_NAME); break; case Request::METHOD_POST: $event->getResponse()->headers->setCookie($this->getCookie($token)); diff --git a/src/Service/Auth/AuthService.php b/src/Service/Auth/AuthService.php index 1bd1d47..4cbbe3e 100644 --- a/src/Service/Auth/AuthService.php +++ b/src/Service/Auth/AuthService.php @@ -9,6 +9,9 @@ use Symfony\Bundle\SecurityBundle\Security; class AuthService { + public const AUTH_COOKIE_NAME = 'authToken'; + public const AUTH_COOKIE_TTL = 86400; + private const TOKEN_HASH_ALGO = 'sha256'; private array $config; From c4e0d6394dae0168f1c799610dde2f98cc9f2822 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 4 Sep 2025 19:40:05 +0200 Subject: [PATCH 4/5] Add Cookie based authenticator --- config/packages/security.yaml | 2 + src/Security/CookieAuthenticator.php | 56 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 src/Security/CookieAuthenticator.php diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 191ba3a..92747af 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -25,6 +25,8 @@ security: access_token: token_handler: App\Security\AccessTokenHandler + custom_authenticators: + - App\Security\CookieAuthenticator # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall diff --git a/src/Security/CookieAuthenticator.php b/src/Security/CookieAuthenticator.php new file mode 100644 index 0000000..6ad4757 --- /dev/null +++ b/src/Security/CookieAuthenticator.php @@ -0,0 +1,56 @@ +cookies->has(AuthService::AUTH_COOKIE_NAME); + } + + public function authenticate(Request $request): Passport + { + $token = $request->cookies->get(AuthService::AUTH_COOKIE_NAME); + + if (!$token) { + throw new AuthenticationException(); + } + + return new SelfValidatingPassport($this->accessTokenHandler->getUserBadgeFrom($token)); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + return new JsonResponse([ + 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()), + ], Response::HTTP_UNAUTHORIZED); + } +} From c32c08c74b7ba769dd7e3d6120af8ed22770fb23 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 4 Sep 2025 19:44:25 +0200 Subject: [PATCH 5/5] Centralize cookie generation on AuthService --- .../AuthorizationTokenSubscriber.php | 25 +++---------------- src/Security/CookieAuthenticator.php | 2 +- src/Service/Auth/AuthService.php | 19 ++++++++++++++ 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/EventSubscriber/AuthorizationTokenSubscriber.php b/src/EventSubscriber/AuthorizationTokenSubscriber.php index 742ecdd..1c9541f 100644 --- a/src/EventSubscriber/AuthorizationTokenSubscriber.php +++ b/src/EventSubscriber/AuthorizationTokenSubscriber.php @@ -6,7 +6,6 @@ use ApiPlatform\Symfony\EventListener\EventPriorities; use App\ApiResource\User\UserTokenApiResource; use App\Service\Auth\AuthService; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\Event\ViewEvent; @@ -14,7 +13,9 @@ use Symfony\Component\HttpKernel\KernelEvents; final class AuthorizationTokenSubscriber implements EventSubscriberInterface { - public function __construct() {} + public function __construct( + private AuthService $authService, + ) {} public static function getSubscribedEvents() { @@ -24,24 +25,6 @@ final class AuthorizationTokenSubscriber implements EventSubscriberInterface ]; } - private function getCookieTtl(): \DateTimeInterface - { - return (new \DateTime())->add(new \DateInterval(\sprintf('PT%dS', AuthService::AUTH_COOKIE_TTL))); - } - - private function getCookie(string $tokenValue) - { - return new Cookie( - name: AuthService::AUTH_COOKIE_NAME, - value: $tokenValue, - expire: $this->getCookieTtl(), - path: '/', - secure: false, - httpOnly: true, - sameSite: 'Lax' - ); - } - public function onView(ViewEvent $event): void { $resourceClass = $event->getRequest()->attributes->get('_api_resource_class'); @@ -73,7 +56,7 @@ final class AuthorizationTokenSubscriber implements EventSubscriberInterface $event->getResponse()->headers->clearCookie(AuthService::AUTH_COOKIE_NAME); break; case Request::METHOD_POST: - $event->getResponse()->headers->setCookie($this->getCookie($token)); + $event->getResponse()->headers->setCookie($this->authService->generateCookie($token)); break; } } diff --git a/src/Security/CookieAuthenticator.php b/src/Security/CookieAuthenticator.php index 6ad4757..7e8e91f 100644 --- a/src/Security/CookieAuthenticator.php +++ b/src/Security/CookieAuthenticator.php @@ -18,7 +18,7 @@ use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPasspor class CookieAuthenticator extends AbstractAuthenticator { public function __construct( - private AccessTokenHandler $accessTokenHandler + private AccessTokenHandler $accessTokenHandler, ) {} /** diff --git a/src/Service/Auth/AuthService.php b/src/Service/Auth/AuthService.php index 4cbbe3e..0a17a9a 100644 --- a/src/Service/Auth/AuthService.php +++ b/src/Service/Auth/AuthService.php @@ -6,6 +6,7 @@ use App\Entity\User\User; use App\Entity\User\UserToken; use App\Repository\User\UserRepository; use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\HttpFoundation\Cookie; class AuthService { @@ -30,6 +31,24 @@ class AuthService return $this->config; } + private function getCookieTtl(): \DateTimeInterface + { + return (new \DateTime())->add(new \DateInterval(\sprintf('PT%dS', AuthService::AUTH_COOKIE_TTL))); + } + + public function generateCookie(string $token): Cookie + { + return new Cookie( + name: AuthService::AUTH_COOKIE_NAME, + value: $token, + expire: $this->getCookieTtl(), + path: '/', + secure: true, + httpOnly: true, + sameSite: 'Strict' + ); + } + public function generateUserToken(User $user, AuthTokenType $type): UserToken { $token = new UserToken();