Dev BMW

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.
1 месяц назад