Doctrine based authentication mechanism in Symfony2 project Doctrine based authentication mechanism in Symfony2 project symfony symfony

Doctrine based authentication mechanism in Symfony2 project


My thoughts on how the authentication works is that there is some magical Controller which catches the validateLogin action, looks for an entity repository for my User entity, calls findOneBy('username' => $username) and compares the passwords... is this right?

You're wrong. Authentication doesn't involve any controller, that's why you don't specify any in _security_check route. Auth is based on EventDispatcher. Whenever you specify some listener in your firewall (eg. form_login, anonymous, logout etc.) you actually register a new listener for core.security event. Symfony\Component\HttpKernel\Security\Firewall::handle() is a place where these listeners are actually registered.

The general, simplified flow:

  1. User fills login form (_username and _password fields).
  2. Request is handled by Symfony2.
  3. core.security event is fired.
  4. EventDispatcher notifies all listeners.
  5. UsernamePasswordFormAuthenticationListener is fired (handle() method) and checks whether:
    1. URL matches check_path option.
    2. Request has both _username and _password parameters.
  6. Listener tries to authenticate user (attemptAuthentication() method).
  7. Authentication manager fires all registered providers.
  8. Finally, DaoAuthenticationProvider is fired and it tries to retrieve user using Doctrine's user repository class.
  9. If everything is fine UsernamePasswordToken (which contain $user object returned by loadUserByUsername() method) is being returned and user is redirected.

Indeed security mechanism is quite complex and hard to understand (documentation isn't still finished). But when you finally understand how it works then you'll see how powerful mechanism it is.


I wrote my own authentication mechanism and it works fine.

  1. Configuration:

    I'm using custom provider and encoder.

    security.config:    providers:        main:            id:         project.user_repository # DI id. Doctrine's UserRepositry            check_path: /login-check    encoders:        main:            class: Project\SiteBundle\Entity\User            id:    security.encoder.sha512     # DI id. Service %security.encoder.digest.class% (with "sha512" as first parameter)    firewalls:        restricted:            pattern:    /panel/.*            form_login:                 check_path: /login-check        public:            pattern:    /.*            anonymous:  true            form_login:                 check_path: /login-check            logout:     true    access_control:        - { path: /panel/.*, role: ROLE_USER }        - { path: /.*, role: IS_AUTHENTICATED_ANONYMOUSLY }

    As you can see /panel/* is restricted, while /* is public.

  2. Service security.encoder.sha512 is a built-in encoder:

    <service id="security.encoder.sha512" class="%security.encoder.digest.class%">    <argument>sha512</argument></service>
  3. Project\SiteBundle\Entity\User:

    /** * @orm:Entity(repositoryClass="Project\SiteBundle\Repository\UserRepository") */class User implements AdvancedAccountInterface {    /**      * @orm:Id @orm:Column(type="integer")     * @orm:GeneratedValue(strategy="AUTO")     */    protected $id;    /**     * @orm:Column(unique=true, nullable=true)     */    protected $email;    /**     * @orm:Column(unique=true, nullable=true)     */    protected $xmpp;    /**     * @orm:Column(length=128)     */    protected $password;    /**     * @orm:Column(length=16)     */    protected $salt;    // User can be logged in using email address or xmpp adress.    // Dozens of getters/setters here.}
  4. Project\SiteBundle\Repository\UserRepository

    class UserRepository extends EntityRepository implements UserProviderInterface {    public function loadUserByUsername($username) {        $dql = sprintf('            SELECT u            FROM %s u            WHERE u.email = :id OR u.xmpp = :id        ', $this->_entityName);        $user = null;        try {            $user = $this->_em->createQuery($dql)->setParameter('id', $username)->getSingleResult();        } catch (ORMException $e) {            throw new UsernameNotFoundException("User $username not found.", $e->getCode(), $e);        }        return $user;    }    public function loadUserByAccount(AccountInterface $user) {        return $this->loadUserByUsername($user->getUsername());    }}
  5. Security routes and controller is same as yours.


You should use the https://github.com/FriendsOfSymfony/FOSUserBundle FOS UserBundle, it implements all this with Doctrine 2 and has tons of features.


The reason, essentially, why the login page loads again with no error message is because, ironically, your security settings are not set up to allow anonymous access to the login page.