I would probably prefer to check user's permissions before dispatching a message, but let's think how we can approach if it's not a suitable case.
In order to check user permissions, you need to authenticate a user. But in case you're consuming a message asynchronously or executing a console command it's not straightforward, as you don't have an actual user. However, you can pass user id with your message or to a console command.
Let me share my idea of a simple solution for Symfony Messenger. In the Symfony Messenger, there is a concept of Stamps, which allows you to add metadata to your message. In our case it would be useful to pass a user id with a message, so we can authenticate a user within the message handling process.
Let's create a custom stamp to hold a user id. It's a simple PHP class, so no need to register it as a service.
<?php
namespace App\Messenger\Stamp;
use Symfony\Component\Messenger\Stamp\StampInterface;
class AuthenticationStamp implements StampInterface
{
private $userId;
public function __construct(string $userId)
{
$this->userId = $userId;
}
public function getUserId(): string
{
return $this->userId;
}
}
Now we can add the stamp to a message.
$message = new SampleMessage($payload);
$this->messageBus->dispatch(
(new Envelope($message))
->with(new AuthenticationStamp($userId))
);
We need to receive and handle the stamp in order to authenticate a user. Symfony Messenger has a concept of Middlewares, so let's create one to handle stamp when we receive a message by a worker. It would check if the message contains the AuthenticationStamp and authenticate a user if the user is not authenticated at the moment.
<?php
namespace App\Messenger\Middleware;
use App\Messenger\Stamp\AuthenticationStamp;
use App\Repository\UserRepositoryInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class AuthenticationMiddleware implements MiddlewareInterface
{
private $tokenStorage;
private $userRepository;
public function __construct(TokenStorageInterface $tokenStorage, UserRepositoryInterface $userRepository)
{
$this->tokenStorage = $tokenStorage;
$this->userRepository = $userRepository;
}
public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
/** @var AuthenticationStamp|null $authenticationStamp */
if ($authenticationStamp = $envelope->last(AuthenticationStamp::class)) {
$userId = $authenticationStamp->getUserId();
$token = $this->tokenStorage->getToken();
if (null === $token || $token instanceof AnonymousToken) {
$user = $this->userRepository->find($userId);
if ($user) {
$this->tokenStorage->setToken(new UsernamePasswordToken(
$user,
null,
'provider',
$user->getRoles())
);
}
}
}
return $stack->next()->handle($envelope, $stack);
}
}
Let's register it as a service (or autowire) and include into the messenger configuration definition.
framework:
messenger:
buses:
messenger.bus.default:
middleware:
- 'App\Messenger\Middleware\AuthenticationMiddleware'
That's pretty much it. Now you should be able to use your regular way to check user's permissions, for example, voters.
As for console command, I would go for an authentication service, which would authenticate a user if the user id is passed to a command.