We want to log the following events in the system: successful login, failed login, and logout.
We will use events to handle these occurrences. A list of them can be found on the Symfony website:
https://symfony.com/doc/7.4/security.html
We are interested in the following events:LoginSuccessEventLoginFailureEventLogoutEvent
src/EventSubscriber/ActivityLogSubscriber.php
<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Http\Event\LogoutEvent;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\User\User;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
class class ActivityLogSubscriber implements EventSubscriberInterface
{
public function __construct(
private EntityManagerInterface $em,
private ActivityLogFactory $activityLogFactory
) {
}
public function onLoginSuccessEvent(LoginSuccessEvent $event): void
{
/** @var User $user */
$user = $event->getUser();
// log to entity $activity: $user, $event->getRequest()->getClientIp()
$this->em->persist($activityLog);
$this->em->flush();
}
public function onLoginFailureEvent(LoginFailureEvent $event): void
{
$passport = $event->getPassport();
try {
/** @var User $user */
$user = $passport->getUser();
} catch (\LogicException $ex) {
//When there is a limit on the number of failed login attempts, the user object is empty after the limit is exceeded, and the User Loader is not set.
throw new UserNotFoundException();
}
// log to entity $activityLog: $user, $event->getRequest()->getClientIp()
$this->em->persist($activityLog);
$this->em->flush();
}
public function onLogoutEvent(LogoutEvent $event): void
{
$token = $event->getToken();
if (null === $token) {
return; // może być już wylogowany
}
/** @var User $user */
$user = $token->getUser();
// log to entity $activityLog: $user, $event->getRequest()->getClientIp()
$this->em->persist($activityLog);
$this->em->flush();
}
public static function getSubscribedEvents(): array
{
return [
LoginSuccessEvent::class => 'onLoginSuccessEvent',
LoginFailureEvent::class => 'onLoginFailureEvent',
LogoutEvent::class => 'onLogoutEvent',
];
}
}In the onLoginFailureEvent, when using a Rate Limiter, an error may occur after the limit for failed login attempts is reached:
No user loader is configured, did you forget to register the "Symfony\Component\Security\Http\EventListener\UserProviderListener" listener?
The exception is thrown from the getUser method of the UserBadge class:vendor/symfony/security-http/Authenticator/Passport/Badge/UserBadge.php
We handle this by catching the LogicException.
In the onLogoutEvent, however, you need to handle the situation where the user has already logged out or the session has expired (empty token).
If you are using Azure, you also need to configure Symfony to return the client’s actual IP address instead of the load balancer’s IP.
This is described in more detail in the Symfony documentation:
https://symfony.com/doc/7.4/deployment/proxies.html
config/packages/framework.yaml
framework:
trusted_proxies: '127.0.0.1,REMOTE_ADDR'
trusted_headers: ['forwarded', 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-prefix']

Leave a Reply