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: 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/EventSubscriber/AuthorizationTokenSubscriber.php b/src/EventSubscriber/AuthorizationTokenSubscriber.php new file mode 100644 index 0000000..1c9541f --- /dev/null +++ b/src/EventSubscriber/AuthorizationTokenSubscriber.php @@ -0,0 +1,63 @@ + ['onView', EventPriorities::POST_WRITE], + KernelEvents::RESPONSE => ['onResponse', EventPriorities::POST_RESPOND], + ]; + } + + 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(AuthService::AUTH_COOKIE_NAME); + break; + case Request::METHOD_POST: + $event->getResponse()->headers->setCookie($this->authService->generateCookie($token)); + break; + } + } +} diff --git a/src/Security/CookieAuthenticator.php b/src/Security/CookieAuthenticator.php new file mode 100644 index 0000000..7e8e91f --- /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); + } +} diff --git a/src/Service/Auth/AuthService.php b/src/Service/Auth/AuthService.php index 1bd1d47..0a17a9a 100644 --- a/src/Service/Auth/AuthService.php +++ b/src/Service/Auth/AuthService.php @@ -6,9 +6,13 @@ 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 { + public const AUTH_COOKIE_NAME = 'authToken'; + public const AUTH_COOKIE_TTL = 86400; + private const TOKEN_HASH_ALGO = 'sha256'; private array $config; @@ -27,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();