I can think of a more proper way, IMHO, to solve this:
The User entity will have an additional property $loginAttempts that will be incremented by the incrementLoginAttempts() method each time the login fails. Its will be initialized to 0 through the ORM and a method isLocked() will tell us if we've reached 5 attempts.
<?php
// AppBundle/Entity/User.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
/**
* @ORM\Entity
* @ORM\Table(name="`user`")
*/
class User extends BaseUser
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer")
*/
protected $id;
public function __construct()
{
parent::__construct();
}
/**
* @ORM\Column(type="integer",options={"default"=0})
*/
private $loginAttempts;
...
public function getLoginAttempts()
{
return $this->loginAttemps;
}
public function incrementLoginAttempts()
{
if($this->loginAttempts<5){
$this->loginAttempts++;
}
return $this;
}
public function isLocked()
{
return ($this->loginAttempts == 5)
}
public function resetLoginAttempts()
{
$this->loginAttempts =0;
return $this;
}
Then, create an EventSubscriber to the SecuritySubscriber event, and fire an incrementLoginAttempts() each time the login fails; at the same time check if the user has already been locked or not yet
<?php
// src/AppBundle/EventSubscriber/SecuritySubscriber.php
namespace AppBundle\EventSubscriber;
use AppBundle\Entity\User;
class SecuritySubscriber implements EventSubscriberInterface
{
private $entityManager;
private $tokenStorage;
private $authenticationUtils;
public function __construct(EntityManager $entityManager, TokenStorageInterface $tokenStorage, AuthenticationUtils $authenticationUtils)
{
$this->entityManager = $entityManager;
$this->tokenStorage = $tokenStorage;
$this->authenticationUtils = $authenticationUtils;
}
public static function getSubscribedEvents()
{
return array(
AuthenticationEvents::AUTHENTICATION_FAILURE => 'onAuthenticationFailure',
);
}
public function onAuthenticationFailure( AuthenticationFailureEvent $event )
{
$existingUser = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $username]);
if ($existingUser) {
$existingUser->incrementLoginAttempts();
$this->entityManager->persist($existingUser);
$this->entityManager->flush();
if($existingUser->isLocked()){
// Do your logic here
// Do not forget to un $existingUser->resetLoginAttempts() when necessary
}
}
}
}
Don't forget to register the subscriber as a service
# app/config/services.yml
services:
app.security.authentication_event_listener:
class: AppBundle\EventSubscriber\SecuritySubscriber
arguments:
- "@doctrine.orm.entity_manager"
- "@security.token_storage"
- "@security.authentication_utils"
P.S: The code has not been tested.