SignatureCheckListener With Symfony’s AsEventListener Attribute
# .env file
APP_CHECK_SIGNATURE=true
<?php
namespace App\Listener;
use App\Enum\Headers;
use App\Exception\UnmatchedSignatureException;
use App\Service\SignatureService;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
#[AsEventListener]
class SignatureCheckListener
{
public function __construct(
#[Autowire(env: 'bool:APP_CHECK_SIGNATURE')]
private readonly bool $checkSignature,
private readonly TokenStorageInterface $tokenStorage,
private readonly SignatureService $signatureService,
private readonly LoggerInterface $logger,
)
{
}
/**
* @param RequestEvent $event
* @return void
* @throws UnmatchedSignatureException
*/
public function __invoke(RequestEvent $event): void
{
if (!$this->checkSignature) {
return;
}
$request = $event->getRequest();
$signature = $request->headers->get(Headers::SIGNATURE->value) ?? '';
$user = $this->tokenStorage->getToken()?->getUser();
if (!$this->signatureService->check($user, $request, $signature)) {
$this->logger->warning(sprintf('Unmatched Signature: %s', $signature));
throw new UnmatchedSignatureException();
}
}
}
The AsEventListener attribute accepts the following parameters.
/symfony/event-dispatcher/Attribute/AsEventListener.php
/**
* @param string|null $event The event name to listen to
* @param string|null $method The method to run when the listened event is triggered
* @param int $priority The priority of this listener if several are declared for the same event
* @param string|null $dispatcher The service id of the event dispatcher to listen to
*/
Storing the header parameters in a PHP Enum can be very useful for easier management.
<?php
namespace App\Enum;
enum Headers: string
{
case SIGNATURE = 'X-Signature';
}
We’re creating a service to calculate the signature.
<?php
namespace App\Service;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface;
class SignatureService
{
public function __construct()
{
}
public function check(?UserInterface $user, Request $request, string $signature): bool
{
if (hash_equals($signature, $this->calculateSignature($user, $request))) {
return true;
}
return false;
}
private function calculateSignature(?UserInterface $user, Request $request): string
{
//your algorithm, this is example
return 'your_calculated_signature';
}
}
Essentially, we aim to re-authenticate a request encrypted with a flag known to both the user and the server, thereby protecting the request from MITM (Man-in-the-middle) attacks. You can explore the Diffie-Hellman algorithm to securely store a private key on the user side without transmitting it over the network.
5 месяцев назад