Introduction
I previously wrote a very popular article called Symfony AD Integration which uses FOSUserBundle and FR3DLdapBundle, and I wanted to provide a simpler method that uses the Symfony LDAP Component. The main difference with this installation is that it simply authenticates against the server, no user information is stored or managed. If your application needs some type of user-to-application management, then you will need to look at using FOSUserBundle as well.
Documentation
The Symfony doc Authenticating against an LDAP server provides most of the details on the LDAP component and how it is used. I simply provide details here on my working example that authenticates to a Microsoft Active Directory (LDAP) server.
Login Controller
You’ll need to create a route with the path “/login” for your users to login and authenticate to. Below is example working code:
<?php // src/AppBundle/Controller/DefaultController.php namespace AppBundle\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; class DefaultController extends Controller { ... /** * @Route("/login", name="login") */ public function loginAction(Request $request, AuthenticationUtils $authUtils) { // get the login error if there is one $error = $authUtils->getLastAuthenticationError(); // last username entered by the user $lastUsername = $authUtils->getLastUsername(); return $this->render('security/login.html.twig', array( 'last_username' => $lastUsername, 'error' => $error, )); } ...
Login Form Twig Code
Below is the Twig template code for the login form:
{# app/Resources/views/security/login.html.twig #} {% extends 'base.html.twig' %} {% block body %} {% if error %} <div>{{ error.messageKey|trans(error.messageData, 'security') }}</div> {% endif %} <form action="{{ path('login') }}" method="post"> <label for="username">Username:</label> <input type="text" id="username" name="_username" value="{{ last_username }}" /> <label for="password">Password:</label> <input type="password" id="password" name="_password" /> {# If you want to control the URL the user is redirected to on success (more details below) <input type="hidden" name="_target_path" value="/account" /> #} <button type="submit">login</button> </form> {% endblock %} {% block stylesheets %} {% endblock %}
The above twig code just provides a username and password input fields, the form gets posted back to itself and if there are errors, they are shown in the error division tag.
Services YAML File
Below is the services.yml file. The main difference between this and the Symfony docs, is that for the dn_string, I only had to specify “MYDOMAIN\{username}”. The variable {username} is retrieved from the login form automatically. The prefix “MYDOMAIN\” is what you would normally put in for a domain name when you log in to Active Directory with a Windows computer. In this case “MYDOMAIN” is an example, and you should use what you would normally enter in your environment.
# app/config/services.yml services: ... Symfony\Component\Ldap\Ldap: arguments: ['@Symfony\Component\Ldap\Adapter\ExtLdap\Adapter'] Symfony\Component\Ldap\Adapter\ExtLdap\Adapter: arguments: - host: 192.168.100.138 port: 389 #encryption: tls options: protocol_version: 3 referrals: false
Notice that “encryption” is commented out. This was a major problem for me; so I suggest you comment it out first.
Security YAML File
The import parts of the security.yml file are the base_dn should include your domain’s “Users” CN (Common Name). You might want to download a copy of Softerra’s LDAP Browser – it’s free, and use this to figure out what you need to enter for your base_dn and search_dn. The search_dn will be an Active Directory account that is allowed to search throughout the directory. Then the search_password is the user account’s password. If you use the Softerra LDAP Browser, you would use the same account information to connect to and browse Active Directory.
# app/config/security.yml security: providers: my_ldap: ldap: service: Symfony\Component\Ldap\Ldap base_dn: 'CN=Users,DC=somedomain,DC=com' search_dn: 'CN=LDAP_ADMIN,CN=Users,DC=somedomain,DC=com' search_password: 'mypass' default_roles: ROLE_USER firewalls: # disables authentication for assets and the profiler, adapt it according to your needs dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: anonymous: ~ form_login_ldap: #http_basic_ldap: login_path: login check_path: login service: Symfony\Component\Ldap\Ldap dn_string: 'MYDOMAIN\{username}' access_control: - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } #- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/user_agent, roles: ROLE_USER }
In the above config, I set the path “/user_agent” to only allow authenticated users (ROLE_USER) to browse the path. So once you enter that path in your browser URL, you will be prompted to login. Then enter your user name (without the domain) and password, and you should get authenticated.
Notice in the security.yml file that I commented out “http_basic_ldap”. If you are having issues logging in, I suggest uncommenting this line and then commenting “form_login_ldap”. Then try logging in with only HTTP Basic authentication. What I found is the debug logs show a lot of useful information in this case. Logs are stored in the “var/logs” folder.
Could you write something more about Softerra’s LDAP Browser and how can i get entry to base_dn and search_dn from this program?
Hi there jr47. Please take a look at my answer to this post on Stackoverflow, it might be helpful:
https://stackoverflow.com/questions/46124750/connect-bind-to-active-directory-windows-using-ldap-in-symfony-3-without-using/46125178#46125178
Trying to set this up with Symfony 3.3, and getting to the point where it does seem to check against my LDAP server – the form correctly returns whether or not the username exists. But it doesn’t authenticate the password, even when typed correctly.
Any suggestions on debugging this?
ldap_provider:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: ‘ou=Expedient,dc=cbb,dc=local’
search_dn: ‘CN=svccolab,OU=Service Accounts,OU=Resources,DC=cbb,DC=local’
search_password: ‘!’
default_roles: ‘ROLE_USER’
uid_key: ‘sAMAccountName’
filter: ‘({uid_key}={username})’
main:
pattern: ^/
provider: ldap_provider
# no config for anonymous
anonymous: ~
# authenticate against cbb ldap / ad server
form_login_ldap:
login_path: login
check_path: login
service: Symfony\Component\Ldap\Ldap
dn_string: ‘ou=Expedient,dc=cbb,dc=local’
Looking at the log, it throws the bind error:
[2017-10-24 03:30:07] php.DEBUG: Warning: ldap_bind(): Unable to bind to server: Invalid credentials {“exception”:”[object] (Symfony\\Component\\Debug\\Exception\\SilencedErrorContext: {\”severity\”:2,\”file\”:\”C:\\\\Users\\\\geoff.maddock\\\\PhpstormProjects\\\\cadence\\\\vendor\\\\symfony\\\\symfony\\\\src\\\\Symfony\\\\Component\\\\Ldap\\\\Adapter\\\\ExtLdap\\\\Connection.php\”,\”line\”:53,\”trace\”:[{\”file\”:\”C:\\\\Users\\\\geoff.maddock\\\\PhpstormProjects\\\\cadence\\\\vendor\\\\symfony\\\\symfony\\\\src\\\\Symfony\\\\Component\\\\Ldap\\\\Ldap.php\”,\”line\”:38,\”function\”:\”bind\”,\”class\”:\”Symfony\\\\Component\\\\Ldap\\\\Adapter\\\\ExtLdap\\\\Connection\”,\”type\”:\”->\”}],\”count\”:1})”} []
Which in LDAP.php is thrown at:
$this->adapter->getConnection()->bind($dn, $password);
I do prefer to see these posts on stackoverflow. There are a lot of people that can help there.
Follow my config EXACTLY first and get your system working, then make your CUSTOM changes. In other words, get rid of “uid_key” and “filter”; also the search_dn needs to be the DN of the LDAP admin user’s account. Your dn_string doesn’t look right either. Should be the windows domain name in the format: DOMAIN\{username} You should also try the http_basic_ldap setting first, and check your “var/logs” and the log files there.