<?php
namespace Sq\GraphQL\Security\Authenticator;
use Psr\Log\LoggerInterface;
use Sq\GraphQL\Security\TokenManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
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\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class PasetoAuthenticator extends AbstractAuthenticator
{
use ImpersonationTrait;
protected const AUTH_HEADER = 'Authorization';
protected const IMPERSONATE_HEADER = 'Impersonate-Uid';
protected const BEARER_REGEX = '/^(?:\s+)?Bearer\s/';
public function __construct(
protected TokenManager $tokenManager,
protected LoggerInterface $logger,
protected RequestStack $requestStack,
) {
}
public function createToken(Passport $passport, string $firewallName): TokenInterface
{
$token = parent::createToken($passport, $firewallName);
$impersonateUid = $this->requestStack->getMainRequest()->headers->get(static::IMPERSONATE_HEADER);
if ($impersonateUid)
{
$token = $this->createImpersonationToken($token, (int) $impersonateUid, $firewallName);
}
return $token;
}
public function supports(Request $request): bool
{
return $request->headers->has(static::AUTH_HEADER)
&& preg_match(static::BEARER_REGEX, $request->headers->get(static::AUTH_HEADER));
}
public function authenticate(Request $request): Passport
{
$header = $request->headers->get(static::AUTH_HEADER);
$token = trim(preg_replace(static::BEARER_REGEX, '', $header));
return new SelfValidatingPassport(new UserBadge((string) $this->tokenManager->getAccessTokenOwnerId($token)));
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
$this->logger && $this->logger->debug('Authentication failure', [
'exception' => $exception,
'guard' => __CLASS__,
]);
// Normally we would throw an exception or return a redirect response back to the login page, but...
// Similar to onAuthenticationSuccess(), continue execution flow but with no authenticated user. This is because
// this particular guard is for GraphQL and we want all errors (including auth errors resulting from no-one logged
// in) to be handled by the GraphQL controller so it can return a properly formatted GraphQL error response.
return null;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response
{
// As this is authenticating for an API and authenticated on every request, just continue the request onto the
// appropriate controller instead of returning a response here (for a successful login page or redirect to last
// page user visited, etc).
return null;
}
}