title: Multiple authentication in Symfony 5 with LDAP and DB
body: <p>I woul'd like to do a multiple authentication in Symfony 5.
  The first authentication is LDAP. If the user is in it's ok. He will be connected.
  If he is not in I want symfony checks in DB if the user exists. If it's a yes so the user will be connected.</p>
  <p>I tried to deploy LDAP authenticator and Security authenticator but... It's always LDAP authenticator who works. Never the security.</p>
  <p>How does it works and what I have to do ?</p>
  <pre class="lang-yaml prettyprint-override"><code># app/config/services.yaml
  
  # This file is the entry point to configure your own services.
  # Files in the packages/ subdirectory configure your dependencies.
  
  # Put parameters here that don't need to change on each machine where the app is deployed
  # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
  parameters:
  
  services:
      # default configuration for services in *this* file
      _defaults:
          autowire: true      # Automatically injects dependencies in your services.
          autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
  
      Symfony\Component\Ldap\Ldap:
          arguments: ['@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
      Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
          arguments:
              -   host: ldap.example.com
                  port: 389
                  #encryption: tls
                  options:
                      protocol_version: 3
                      referrals: false
  
      # makes classes in src/ available to be used as services
      # this creates a service per class whose id is the fully-qualified class name
      App\:
          resource: '../src/*'
          exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
  
      # controllers are imported separately to make sure services can be injected
      # as action arguments even if you don't extend any base controller class
      App\Controller\:
          resource: '../src/Controller'
          tags: ['controller.service_arguments']
  
      # add more service definitions when explicit configuration is needed
      # please note that last definitions always *replace* previous ones
  </code></pre>
  <pre class="lang-yaml prettyprint-override"><code># app/config/packages/security.yaml
  
  security:
      encoders:
          App\Entity\User:
              algorithm: auto
  
      # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
      providers:
          my_ldap:
              ldap:
                  service: Symfony\Component\Ldap\Ldap
                  base_dn: dc=example,dc=com
                  search_dn: cn=username,ou=Administration,dc=example,dc=com
                  search_password: userPassword
                  default_roles: ROLE_USER
  
          app_user_provider:
              entity:
                  class: App\Entity\User
                  property: email
                  
      firewalls:
          dev:
              pattern: ^/(_(profiler|wdt)|css|images|js)/
              security: false
          ldap:
              anonymous: ~
               
              form_login_ldap:
                  login_path: login
                  check_path: login
                  service: Symfony\Component\Ldap\Ldap
                  dn_string: ou=aGroupWhereAreMyUsers,dc=example,dc=com
                  query_string: '(samaccountname={username})'
                  search_dn: cn=username,ou=Administration,dc=example,dc=com
                  search_password: userPassword
              
              logout:
                  path: app_logout
  
          main:
              anonymous: ~
              provider: app_user_provider
              logout:
                  path: app_logout
              guard:
                  authenticators:
                      - App\Security\LoginAuthenticator
          
      access_control:
          - { path: ^/$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
          - { path: ^/info$, roles: ROLE_USER }
  
      role_hierarchy:
          ROLE_SUPER_ADMIN: [ROLE_SUPER_ADMIN, ROLE_USER]
          ROLE_USER: ROLE_USER
  </code></pre>
  <pre class="lang-php prettyprint-override"><code>//app/src/Security/LoginAuthenticator.php
  
  &lt;?php
  
  namespace App\Security;
  
  use App\Entity\User;
  use Doctrine\ORM\EntityManagerInterface;
  use Symfony\Component\HttpFoundation\RedirectResponse;
  use Symfony\Component\HttpFoundation\Request;
  use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
  use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
  use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
  use Symfony\Component\Security\Core\Security;
  use Symfony\Component\Security\Core\User\UserInterface;
  use Symfony\Component\Security\Core\User\UserProviderInterface;
  use Symfony\Component\Security\Csrf\CsrfToken;
  use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
  use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
  use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
  use Symfony\Component\Security\Http\Util\TargetPathTrait;
  
  class LoginAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
  {
      use TargetPathTrait;
  
      public const LOGIN_ROUTE = 'login';
  
      private $entityManager;
      private $urlGenerator;
      private $csrfTokenManager;
      private $passwordEncoder;
  
      public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
      {
          $this-&gt;entityManager = $entityManager;
          $this-&gt;urlGenerator = $urlGenerator;
          $this-&gt;csrfTokenManager = $csrfTokenManager;
          $this-&gt;passwordEncoder = $passwordEncoder;
      }
  
      public function supports(Request $request)
      {
          return self::LOGIN_ROUTE === $request-&gt;attributes-&gt;get('_route')
              &amp;&amp; $request-&gt;isMethod('POST');
      }
  
      public function getCredentials(Request $request)
      {
          $credentials = [
              'email' =&gt; $request-&gt;request-&gt;get('email'),
              'password' =&gt; $request-&gt;request-&gt;get('password'),
              'csrf_token' =&gt; $request-&gt;request-&gt;get('_csrf_token'),
          ];
          $request-&gt;getSession()-&gt;set(
              Security::LAST_USERNAME,
              $credentials['email']
          );
  
          return $credentials;
      }
  
      public function getUser($credentials, UserProviderInterface $userProvider)
      {
          $token = new CsrfToken('authenticate', $credentials['csrf_token']);
          if (!$this-&gt;csrfTokenManager-&gt;isTokenValid($token)) {
              throw new InvalidCsrfTokenException();
          }
  
          $user = $this-&gt;entityManager-&gt;getRepository(User::class)-&gt;findOneBy(['email' =&gt; $credentials['email']]);
  
          if (!$user) {
              // fail authentication with a custom error
              throw new CustomUserMessageAuthenticationException('Email could not be found.');
          }
  
          return $user;
      }
  
      public function checkCredentials($credentials, UserInterface $user)
      {
          return $this-&gt;passwordEncoder-&gt;isPasswordValid($user, $credentials['password']);
      }
  
      /**
       * Used to upgrade (rehash) the user's password automatically over time.
       */
      public function getPassword($credentials): ?string
      {
          return $credentials['password'];
      }
  
      public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
      {
          if ($targetPath = $this-&gt;getTargetPath($request-&gt;getSession(), $providerKey)) {
              return new RedirectResponse($targetPath);
          }
  
          // For example : return new RedirectResponse($this-&gt;urlGenerator-&gt;generate('some_route'));
          throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
      }
  
      protected function getLoginUrl()
      {
          return $this-&gt;urlGenerator-&gt;generate(self::LOGIN_ROUTE);
      }
  }
  </code></pre>
  <pre class="lang-php prettyprint-override"><code>//app/src/Controller/defaultController.php
  
  &lt;?php
  
  namespace App\Controller;
  
  use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  use Symfony\Component\Routing\Annotation\Route;
  use Symfony\Component\HttpFoundation\Request;
  use Symfony\Component\HttpFoundation\Response;
  use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
   
  
  class DefaultController extends AbstractController
  {
      /**
       * @Route(&quot;/info&quot;, name=&quot;default&quot;)
       */
      public function index()
      {
          return $this-&gt;render('default/index.html.twig', [
              'controller_name' =&gt; 'DefaultController',
              'user' =&gt; $this-&gt;getUser(),
          ]);
      }
  
      /**
       * @Route(&quot;/&quot;, name=&quot;login&quot;)
       */
      public function loginAction(Request $request, AuthenticationUtils $authUtils): Response
      {
          // get the login error if there is one
          $error = $authUtils-&gt;getLastAuthenticationError();
          
          // last username entered by the user
          $lastUsername = $authUtils-&gt;getLastUsername();
  
          if(!is_null($this-&gt;getUser()) &amp;&amp; in_array('CN=userHasToBeInThisGroupToAccessToTheWebsite,OU=Administration,DC=example,DC=com', $this-&gt;getUser()-&gt;getEntry()-&gt;getAttributes()['memberOf'])){
  
              if(in_array('CN=groupWhereUsersWhoAreInAreAdmins,OU=Administration,DC=example,DC=com', $this-&gt;getUser()-&gt;getEntry()-&gt;getAttributes()['memberOf'])){
                  //Insert the code here to add &quot;ROLE_SUPER_ADMIN&quot; to the user before he goes on the dashboard
              }
  
              return $this-&gt;redirectToRoute('default');
          }
  
  
          return $this-&gt;render('security/login.html.twig', array(
              'last_username' =&gt; $lastUsername,
              'error'         =&gt; $error,
          ));
      }
  
      /**
       * @Route(&quot;/logout&quot;, name=&quot;app_logout&quot;)
       */
      public function logout()
      {
          // controller can be blank: it will never be executed!
          throw new \Exception('Don\'t forget to activate logout in security.yaml');
      }
  
  }
  </code></pre>
  <pre><code>{# app/templates/security/login.html.twig #}
  
  {% extends 'base.html.twig' %}
   
  {% block body %}
      {% if error %}
          &lt;div class=&quot;alert alert-danger&quot;&gt;{{ error.messageKey|trans(error.messageData, 'security') }}&lt;/div&gt;
      {% endif %}
       
      &lt;form method=&quot;post&quot;&gt;
          &lt;label for=&quot;username&quot;&gt;Username:&lt;/label&gt;
          &lt;input type=&quot;text&quot; id=&quot;username&quot; name=&quot;_username&quot; value=&quot;{{ last_username }}&quot; required /&gt;
       
          &lt;label for=&quot;password&quot;&gt;Password:&lt;/label&gt;
          &lt;input type=&quot;password&quot; id=&quot;password&quot; name=&quot;_password&quot; required /&gt;
  
          &lt;input type=&quot;hidden&quot; name=&quot;_csrf_token&quot; value=&quot;{{ csrf_token('authenticate') }}&quot; /&gt;
       
          &lt;button type=&quot;submit&quot;&gt;login&lt;/button&gt;
      &lt;/form&gt;
  
      &lt;a href='/logout'&gt; Logout &lt;/a&gt;
  {% endblock %}
  </code></pre>