title: Symfony: Authenticate users against LDAP server, but allow login only if their username is in custom db table
body: <p>First a short explanation of my task. I'm using Symfony 2.8 and have an application with REST API and SonataAdminBundle. Visitors of the website can post certain data over the REST API that is persisted to the database. A certain group of employees should manage those data through the admin area.</p>
  
  <p>The access to the admin area should be protected with username and password. There is entity <code>Employee</code> with a property <code>username</code>, but no password. The authentication should be done against the LDAP server, but the access to admin area should be restricted only to those employees that are present in the entity <code>Employee</code> i.e. the referring database table.</p>
  
  <p>For the LDAP authentication I am using the new LDAP component in Symfony 2.8.</p>
  
  <p>Beyond that there should be an admin account as <code>in_memory</code> user.</p>
  
  <p>This is what I have now:</p>
  
  <p><code><b>app/config/services.yml</b></code></p>
  
  <pre><code>services:
      app.ldap:
          class: Symfony\Component\Ldap\LdapClient
          arguments: ["ldaps://ldap.uni-rostock.de"]
  
      app.db_user_provider:
          class: AppBundle\Security\DbUserProvider
          arguments: ["@doctrine.orm.entity_manager"]
  </code></pre>
  
  <p><code><b>app/config/security.yml</b></code></p>
  
  <pre><code>security:
      role_hierarchy:
          ROLE_ADMIN:       ROLE_USER
          ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
  
      providers:
          chain_provider:
              chain:
                  providers: [db_user, app_users]
  
          in_memory:
              memory:
                  users:
                      admin: { password: adminpass, roles: 'ROLE_ADMIN' }
  
          app_users:
              ldap:
                  service: app.ldap
                  base_dn: ou=people,o=uni-rostock,c=de
                  search_dn: uid=testuser,ou=people,o=uni-rostock,c=de
                  search_password: testpass
                  filter: (uid={username})
                  default_roles: ROLE_USER
  
          db_user:
              id: app.db_user_provider
  
      firewalls:
          dev:
              pattern: ^/(_(profiler|wdt)|css|images|js)/
              security: false
  
          admin:
              anonymous: true
              pattern:   ^/
              form_login_ldap:
                  provider: chain_provider
                  service: app.ldap
                  dn_string: "uid={username},ou=people,o=uni-rostock,c=de"
                  check_path: /login_check
                  login_path: /login
              form_login:
                  provider: in_memory
                  check_path: /login_check
                  login_path: /login
              logout:
                  path: /logout
                  target: /
  
      access_control:
          - { path: ^/admin, roles: ROLE_USER }
  
      encoders:
          Symfony\Component\Security\Core\User\User: plaintext
          AppBundle\Entity\Employee: bcrypt
  </code></pre>
  
  <p><code><b>src/AppBundle/Entity/Employee.php</b></code>
      
  
  <pre><code>namespace AppBundle\Entity;
  
  use Symfony\Component\Security\Core\User\UserInterface;
  use Symfony\Component\Security\Core\User\EquatableInterface;
  use Doctrine\ORM\Mapping as ORM;
  
  class Employee implements UserInterface, EquatableInterface
  {
      // other properties
  
      private $username;
  
      // getters and setters for the other properties
  
      public function getUsername()
      {
          return $this-&gt;username;
      }
  
      public function getRoles()
      {
          return array('ROLE_USER');
      }
  
      public function getPassword()
      {
          return null;
      }
  
      public function getSalt()
      {
          return null;
      }
  
      public function eraseCredentials()
      {
      }
  
      public function isEqualTo(UserInterface $user)
      {
          if (!$user instanceof Employee) {
              return false;
          }
  
          if ($this-&gt;username !== $user-&gt;getUsername()) {
              return false;
          }
  
          return true;
      }
  }
  </code></pre>
  
  <p><code><b>src/AppBundle/Security/DbUserProvider.php</b></code></p>
  
  <pre><code>&lt;?php
  
  namespace AppBundle\Security;
  
  use Symfony\Component\Security\Core\User\UserProviderInterface;
  use Symfony\Component\Security\Core\User\UserInterface;
  use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
  use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
  use Doctrine\ORM\EntityManager;
  use AppBundle\Entity\Employee;
  
  class DbUserProvider implements UserProviderInterface
  {
      private $em;
  
      public function __construct(EntityManager $em)
      {
          $this-&gt;em = $em;
      }
  
      public function loadUserByUsername($username)
      {
          $repository = $this-&gt;em-&gt;getRepository('AppBundle:Employee');
          $user = $repository-&gt;findOneByUsername($username);
  
          if ($user) {
              return new Employee();
          }
  
          throw new UsernameNotFoundException(
              sprintf('Username "%s" does not exist.', $username)
          );
      }
  
      public function refreshUser(UserInterface $user)
      {
          if (!$user instanceof Employee) {
              throw new UnsupportedUserException(
                  sprintf('Instances of "%s" are not supported.', get_class($user))
              );
          }
  
          return $this-&gt;loadUserByUsername($user-&gt;getUsername());
      }
  
      public function supportsClass($class)
      {
          return $class === 'AppBundle\Entity\Employee';
      }
  }
  </code></pre>
  
  <p>The authentication against LDAP works like a charm. When an employee from the database is trying to login, he/she is redirected to the homepage('/') and the login is failed. All other users, who are not in the database, can login without a problem.</p>
  
  <p><b>That is exactly the opposite of what I want!</b></p>
  
  <p>If I chain the providers like this:</p>
  
  <pre><code>chain_provider:
      chain:
          providers: [app_users, db_user]
  </code></pre>
  
  <p>then the method <code>loadUserByUsername</code> is not even called and all users can login, those who are in the database and those who are not.</p>
  
  <p>The <code>in_memory</code> user admin can login without a problem in any case.</p>
  
  <p>I appreciate any help. If someone thinks that my entire approach is bad and knows a better way, please don't spare with critics.</p>
  
  <p>I know there is FOSUserBundle and SonataUserBundle, but I would prefer a custom user provider as I don't want to bloat the entity Employee, since I really don't need all those properties like password, salt, isLocked, etc. Also I don't think configuring SonataUserBundle in my particular case would be much simpler. Should you still think there is a more elegant way to fulfill my task with these two bundles, I'll be grateful to get a good advice.</p>