1
votes

I need to authenticate a user against an LDAP server with custom logic.

I'm implementing a custom authentication system in Symfony 3.3, and built a custom authenticator named LoginFormAuthenticator that extends AbstractFormLoginAuthenticator, as per: http://symfony.com/doc/current/security/guard_authentication.html

I need to check the username against a User entity in the database, then, depending on the type of user, either auth against a bcrypt password stored in the database, or against an external LDAP server.

Inside of the checkCredentials method, I can validate the password stored in the database successfully with:

    class LoginFormAuthenticator extends AbstractFormLoginAuthenticator {
     ...

            public function checkCredentials($credentials, UserInterface $user)
            {
               ...

               // check password if the user is database user
               if ($user->getApp() == 'DB') {
                 if ($this->passwordEncoder->isPasswordValid($user, $password)) {
                      return true;
                 }
               }

               // check LDAP server if LDAP user
               if ($this->getApp() == 'LDAP') {
                if ($this->unknownLdapService->check($user, $password)
                {
                      return true;
                }
               ...

I'm not clear on the correct way to check the username and password against the LDAP server with native symfony functionality.

If I change my config to use form_login_ldap (and not my new authenticator), it does in fact successfully auth against LDAP, although where it makes the call is obfuscated to me.

What service or class I should be using to query LDAP in place of unknownLdapService above?

2

2 Answers

3
votes

The solution I ended up using here was that I first injected the existing Symfony ldap service into the constructor of my method. The ldap service is configured in services.yml the same way the Symfony docs configure it for the form_login_ldap provider.

/**
 * LoginFormAuthenticator constructor.
 * @param FormFactoryInterface $formFactory
 * @param EntityManager $em
 * @param RouterInterface $router
 * @param SecureUserPasswordEncoder $passwordEncoder
 * @param Ldap $ldap
 */
public function __construct(..., Ldap $ldap, ... )
{
    ...
    $this->ldap = $ldap;
}

Then inside of my checkCredentials method, I called the ldap bind method:

 public function checkCredentials($credentials, $userInterface $user)
    ...
    $password = $credentials['_password']; 
    $login_format = 'DOMAIN\%s';  // this is the expected format in my case
    $login_username = sprintf($login_format, $user);
    ...
    try {
        // try to bind with the username and provided password
        $this->ldap->bind($login_username, $password);
    } catch (\Symfony\Component\Ldap\Exception\ConnectionException $e) {
        //return false;
        throw new CustomUserMessageAuthenticationException('The submitted LDAP password is invalid.');
    };
        return true;
    };

This works, and if the ldap auth fails, it throws the appropriate exception.

0
votes

You can could use your own LDAP service: you just need to call ldap_bind. (it can allows you to do more ldap checks or to mock it aswell)

You could alose use the Symfony provider: vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php

namespace Symfony\Component\Security\Core\Authentication\Provider;

class LdapBindAuthenticationProvider extends UserAuthenticationProvider
{
    private $userProvider;
    private $ldap;
    private $dnString;

    public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, LdapClientInterface $ldap, $dnString = '{username}', $hideUserNotFoundExceptions = true)
    {
        parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);

        $this->userProvider = $userProvider;
        $this->ldap = $ldap;
        $this->dnString = $dnString;
    }

    /**
     * {@inheritdoc}
     */
    protected function retrieveUser($username, UsernamePasswordToken $token)
    {
        if ('NONE_PROVIDED' === $username) {
            throw new UsernameNotFoundException('Username can not be null');
        }

        return $this->userProvider->loadUserByUsername($username);
    }

    /**
     * {@inheritdoc}
     */
    protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
    {
        $username = $token->getUsername();
        $password = $token->getCredentials();

        if ('' === $password) {
            throw new BadCredentialsException('The presented password must not be empty.');
        }

        try {
            $username = $this->ldap->escape($username, '', LDAP_ESCAPE_DN);
            $dn = str_replace('{username}', $username, $this->dnString);

            $this->ldap->bind($dn, $password);
        } catch (ConnectionException $e) {
            throw new BadCredentialsException('The presented password is invalid.');
        }
    }
}