Nomisoft
Menu

Logging login in Symfony

02nd August 2016

If you need to store login attempts or perform some kind of custom action whenever someone logs in to your site you can achieve this using Symfony event listeners.

This assumes you already have login functionality build into your site using Symfonys security system. If you haven't already then please read up more on the security components on the Symfony documentation.

The code

We'll start with creating our 'Login' entity. This will store all login attempts whether they're successful or not. The below is an example but you could add/remove any other fields you wish. We have a username field that will store the username the user tried to login with and a boolean success field to mark if the user successfully logged in or not. We also store the timestamp and the IP address of the user.


#src/AppBundle/Entity/Login.php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Login
 *
 * @ORM\Table(name="login")
 * @ORM\Entity()
 */
class Login
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="username", type="string", length=255)
     */
    private $username;

    /**
     * @var string
     *
     * @ORM\Column(name="ip", type="string", length=15)
     */
    private $ip;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="timestamp", type="datetime")
     */
    private $timestamp;

    /**
     * @var boolean
     *
     * @ORM\Column(name="success", type="boolean")
     */
    private $success;


    public function __construct()
    {
        $this->timestamp = new \DateTime();
    }

    //... getters and setters

}

Next up we create a listener. This listens for "security.authentication.failure" and "security.interactive_login" events from Symfony. When these events are fired we'll create a new Login object and set its username, ip and success variables before persisting and flushing it to the database.


#src/AppBundle/EventListener/AuthenticationListener.php

namespace AppBundle\EventListener;

use AppBundle\Entity\Login;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

class AuthenticationListener
{

    private $doctrine;

    private $request;


    /**
     * @param $doctrine
     * @param $request
     */
    public function __construct($doctrine, $request)
    {
        $this->doctrine = $doctrine;
        $this->request = $request;
    }

    /**
     * onAuthenticationFailure
     *
     * @param AuthenticationFailureEvent $event
     */
    public function onAuthenticationFailure(AuthenticationFailureEvent $event)
    {
        $username = $event->getAuthenticationToken()->getUsername();
        $this->saveLogin($username, false);
    }

    /**
     * onAuthenticationSuccess
     *
     * @param InteractiveLoginEvent $event
     */
    public function onAuthenticationSuccess(InteractiveLoginEvent $event)
    {
        $username = $event->getAuthenticationToken()->getUsername();
        $this->saveLogin($username, true);
    }

    /**
     * onAuthenticationSuccess
     *
     * @param $success
     * @param $username
     */
    private function saveLogin($username, $success) {
        $login = new Login();
        $login->setUsername($username);
        $login->setIp($this->request->getCurrentRequest()->getClientIp());
        $login->setSuccess($success);

        $em = $this->doctrine->getManager();
        $em->persist($login);
        $em->flush();
    }
}

We need to add the listeners to our services.yml file in the app/config folder. You'll notice we are passing the doctrine service and the request to our listener so that we're able to save to our database and also grab the users IP address. We also specify the events we want to listen to and the methods we want to call in the tags section.


services:
    # authentication event listener
    app.security.authentication_event_listener:
        class: AppBundle\EventListener\AuthenticationListener
        arguments: ["@doctrine","@request_stack"]
        tags:
            - { name: kernel.event_listener, event: security.authentication.failure, method: onAuthenticationFailure }
            - { name: kernel.event_listener, event: security.interactive_login, method: onAuthenticationSuccess }

Now whenever anyone attempts to login to our site the attempt will be stored in a our login table. You could potentially use this as a basis for blocking users with too many failed login attempts, i'll detail this in a later tutorial