src/EventSubscriber/TwoFactorSecuritySubscriber.php line 84

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\EventSubscriber;
  4. use App\Entity\User;
  5. use Psr\Log\LoggerInterface;
  6. use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvent;
  7. use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvents;
  8. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  9. use Symfony\Component\HttpFoundation\RequestStack;
  10. use Symfony\Component\RateLimiter\RateLimiterFactory;
  11. /**
  12.  * Security event subscriber for 2FA events.
  13.  * 
  14.  * Handles:
  15.  * - Audit logging for 2FA attempts (success/failure)
  16.  * - Rate limiting for brute-force protection
  17.  */
  18. class TwoFactorSecuritySubscriber implements EventSubscriberInterface
  19. {
  20.     public function __construct(
  21.         private readonly LoggerInterface $logger,
  22.         private readonly RequestStack $requestStack,
  23.         private readonly ?RateLimiterFactory $twoFactorLimiter null,
  24.     ) {
  25.     }
  26.     public static function getSubscribedEvents(): array
  27.     {
  28.         return [
  29.             TwoFactorAuthenticationEvents::SUCCESS => ['onTwoFactorSuccess'0],
  30.             TwoFactorAuthenticationEvents::FAILURE => ['onTwoFactorFailure'0],
  31.             TwoFactorAuthenticationEvents::ATTEMPT => ['onTwoFactorAttempt'10],
  32.             TwoFactorAuthenticationEvents::COMPLETE => ['onTwoFactorComplete'0],
  33.         ];
  34.     }
  35.     /**
  36.      * Called when 2FA attempt is made (before validation).
  37.      * Apply rate limiting here.
  38.      */
  39.     public function onTwoFactorAttempt(TwoFactorAuthenticationEvent $event): void
  40.     {
  41.         $user $event->getToken()->getUser();
  42.         
  43.         if (!$user instanceof User) {
  44.             return;
  45.         }
  46.         $request $this->requestStack->getCurrentRequest();
  47.         $ip $request?->getClientIp() ?? 'unknown';
  48.         // Rate limiting check
  49.         if ($this->twoFactorLimiter !== null) {
  50.             $limiterKey sprintf('2fa_%s_%s'$user->getId(), $ip);
  51.             $limiter $this->twoFactorLimiter->create($limiterKey);
  52.             
  53.             if (!$limiter->consume()->isAccepted()) {
  54.                 $this->logger->warning('2FA rate limit exceeded', [
  55.                     'user_id' => $user->getId(),
  56.                     'email' => $user->getEmail(),
  57.                     'ip' => $ip,
  58.                 ]);
  59.                 
  60.                 // The actual blocking should be handled by the rate limiter configuration
  61.                 // This log entry is for audit purposes
  62.             }
  63.         }
  64.         $this->logger->info('2FA attempt initiated', [
  65.             'user_id' => $user->getId(),
  66.             'email' => $user->getEmail(),
  67.             'ip' => $ip,
  68.             'method' => $user->getTwoFactorMethod(),
  69.         ]);
  70.     }
  71.     /**
  72.      * Called when 2FA code validation succeeds.
  73.      */
  74.     public function onTwoFactorSuccess(TwoFactorAuthenticationEvent $event): void
  75.     {
  76.         $user $event->getToken()->getUser();
  77.         
  78.         if (!$user instanceof User) {
  79.             return;
  80.         }
  81.         $request $this->requestStack->getCurrentRequest();
  82.         $ip $request?->getClientIp() ?? 'unknown';
  83.         $this->logger->info('2FA authentication successful', [
  84.             'user_id' => $user->getId(),
  85.             'email' => $user->getEmail(),
  86.             'ip' => $ip,
  87.             'method' => $user->getTwoFactorMethod(),
  88.             'user_agent' => $request?->headers->get('User-Agent'),
  89.         ]);
  90.     }
  91.     /**
  92.      * Called when 2FA code validation fails.
  93.      */
  94.     public function onTwoFactorFailure(TwoFactorAuthenticationEvent $event): void
  95.     {
  96.         $user $event->getToken()->getUser();
  97.         
  98.         if (!$user instanceof User) {
  99.             return;
  100.         }
  101.         $request $this->requestStack->getCurrentRequest();
  102.         $ip $request?->getClientIp() ?? 'unknown';
  103.         $this->logger->warning('2FA authentication failed', [
  104.             'user_id' => $user->getId(),
  105.             'email' => $user->getEmail(),
  106.             'ip' => $ip,
  107.             'method' => $user->getTwoFactorMethod(),
  108.             'user_agent' => $request?->headers->get('User-Agent'),
  109.         ]);
  110.     }
  111.     /**
  112.      * Called when 2FA process is fully complete (all providers passed).
  113.      */
  114.     public function onTwoFactorComplete(TwoFactorAuthenticationEvent $event): void
  115.     {
  116.         $user $event->getToken()->getUser();
  117.         
  118.         if (!$user instanceof User) {
  119.             return;
  120.         }
  121.         $request $this->requestStack->getCurrentRequest();
  122.         $ip $request?->getClientIp() ?? 'unknown';
  123.         $this->logger->info('2FA authentication complete - full access granted', [
  124.             'user_id' => $user->getId(),
  125.             'email' => $user->getEmail(),
  126.             'ip' => $ip,
  127.         ]);
  128.     }
  129. }