Merge pull request #146 from goteo/feat/token-cookie
[feat] Token cookie
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
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\Request;
|
||||
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
||||
use Symfony\Component\HttpKernel\Event\ViewEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
final class AuthorizationTokenSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private AuthService $authService,
|
||||
) {}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
KernelEvents::VIEW => ['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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
use App\Service\Auth\AuthService;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
|
||||
/**
|
||||
* @see https://symfony.com/doc/current/security/custom_authenticator.html
|
||||
*/
|
||||
class CookieAuthenticator extends AbstractAuthenticator
|
||||
{
|
||||
public function __construct(
|
||||
private AccessTokenHandler $accessTokenHandler,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Called on every request to decide if this authenticator should be
|
||||
* used for the request. Returning `false` will cause this authenticator
|
||||
* to be skipped.
|
||||
*/
|
||||
public function supports(Request $request): ?bool
|
||||
{
|
||||
return $request->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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user