Word of advice: PHP authentication against LDAP service

I see this problem a lot in various PHP scripts which are connecting to LDAP service.. Problem is not isolated to PHP environment only, but for some odd reason, PHP developers in majority end in this pitfall, while developers on using other popular programming languages usually handle this issue better.

Introduction to the problem

LDAP service authentication often poses a challenge to the developers who are engaging in LDAP-dependent services development for the first time. In general, your input data will be username and password, while for the succesful authentication against LDAP you need different info: DN (distinguished name) and a password.

So, in order to be able to authenticate user credentials against LDAP service, you have to make a relation between username as input variable and DN as a parameter LDAP service expects.

Again, LDAP service is very specific with its approach towards users, passwords and authentication. You might be able to retrieve DN using query such as

…even when your server bind is anonymous. Of course, that depends on the configuration of your LDAP service, but it’s not so rare occurrence that LDAP service allows such basic info as DN to be read through anonymous connections. Should it be allowed is a discussion for some  completely another occasion.

But the problem starts just here.

Problematic implementation of LDAP authentication in the client applications

More than often, developer and sysadmin logic points to the direction of setting up the ACLs in LDAP service in a way that at least one (application specific) LDAP user can sweep through the whole LDAP tree and read operational attributes (in this case userPassword, or any other field which contains user passwords).

Idea behind this is that the same logic which is used for binding together username from user input with DN information from LDAP does also the validation of the password.

Then after the DN is found, application reads userPassword (or any other field which contains user passwords in given setup) and compares it with what application thinks password should be (application renders password hash  by itself from the plain-text password user gave on login, and then compares it against the encrypted password [password hash to be more precise] pulled from LDAP):

And voila! Problem solved, isn’t it? Without much complications, application successfully authenticated users against LDAP service.

Well, hold your horses Johhny. :)

Real world problems with above approach

Indeed, code like the one above might seem like a done job for you. Just as easy it might turn out that it does the job perfectly. But there are a couple of obvious problems with this approach:

  1. LDAP services support at least handful of password hashing mechanisms. Above example implements just one (CRYPT hashing mechanism), but you have to implement them all in your application in order for this approach to function properly (it’s way too easy to mix password hashing types when administering LDAP – all the decent administration tools for LDAP support most if not all of password hashing mechanisms your LDAP service is aware of.
  2. You are relying on a fact that your programming language or your development framework of choice have completely the same hashing implementations like your LDAP service. It isn’t necessary the true, although in wide variety of real world use cases it probably is.
    • Even if it is so at the moment, it might not be with some of the next OS/server services/programming language update. Security is a rapidly changing area of computer science and practice.
  3. You are, for this purpose at least, completely unnecessary allowing at least one user from LDAP service to read user passwords. Even though they are usually hashed, I hope you can see the problem of this approach.

And finally, the solution

If you’ve recognized your coding practice in above example, solution is far easier than you would probably think. :)

Instead of having dedicated user on your LDAP system which can read all of the users’ passwords, you only need (for the sake of authentication at least) one which must be able to read, but literally, only uid (or equivalent attribute which holds username in your setup) and DN fields from LDAP.

Then for the start of authentication process you will, just as before, bind as a special user (or not even that – as I’ve mentioned at the beginning of the article – that info might just be readable by anonymously bind user on your LDAP service). You will, again same as before, look for DN based on the user input of username field.

But, in difference to previous practice, this time you are not reading the content of the password field.

Instead, you take newly found DN and bind once again to the LDAP service with newly found DN information and user-provided password.

If retrieving DN failed, user didn’t input correct username.

If binding with properly formed DN you’ve retrieved from LDAP based on the username paired with user-provided password fails, user entered the wrong password (assuming, of course, all is ok with the LDAP service :)).

In practice, it looks kind of like this:

Of course, you will do it in more elegant way. This code is just an easy-to-follow example on how to properly do LDAP authentication from PHP.

Hope you’ve enjoyed this article, other similar ones will follow.

Author: Vedran Krivokuca

A developer living and working in Germany. Wannabe opensource contributor. Feeling strong of some social issues.

7 thoughts on “Word of advice: PHP authentication against LDAP service”

  1. Hi and thanks for your article. I’m pretty new to the LDAP world and I’m implementing, for a PHP application, the possibility to authenticate users also through an LDAP server.

    My approach (which follows) is simpler, because I’m assuming that the base_dn is known in advance and can be set as a configuration parameter. I found several examples similar to mine but after having read your article I realized that probably the assumption is wrong because two users of the application could be under different base_dn, am I right?

    How is usually the real-world scenario?

    $ldap_host = ‘ldap.example.com’;
    $ldap_base_dn = ‘ou=Users,dc=example,dc=com’;

    $ds = ldap_connect($ldap_host) or die (‘Error during ldap connection’);
    ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ds, LDAP_OPT_REFERRALS, 0);

    $username = “john”;
    $password = “pwd”;

    $login = ldap_bind( $ds, ‘cn=’.$username.’,’.$ldap_base_dn, $password ) or die (‘Error during ldap authentication’);

    ldap_unbind($ds);

  2. Just an add: of course my example above missed to filter user’s input and this can lead to security issues, but this is another story.

  3. My article doesn’t really go into the specifics of how you should organize your LDAP tree(s). You probably know yourself how that part is configurable in more ways than imaginable. So, it’s meant only to cover how passwords should be handled when authenticating against LDAP service (too often I see people using adm or adm-alike accounts to read values from LDAP service instead of simply doing bind with the username/password (ldap_bind).

    And that part you got right already, judging by your example piece of code.

  4. Yes, what I meant (and now I’m realizing that your approach probably suffers from the same problem) is that the approach I posted (which is the most popular I guess), doesn’t take into consideration that
    1) if base_dn is too high in the hierarchy, there could be two user having same username and password
    2) in some case you can’t set the base_dn at the right (low) level (the one which guarantee just one uer( because maybe there are two users belonging to two different trees

    So I guess you can verify if you have just one result through a username search and throw an error if you have more, but still it is not an optimal solution.

    PS. the email notify seems not to work (at least in my case)

  5. Identical UIDs in different parts of LDAP tree are not the problem. Due to other factors, typical setup we deploy doesn’t allow duplicate UIDs within the range of the same service/cluster of services. There are, of course, pros and cons to that approach, so your mileage may vary. I have to say though that this approach has been working very well for us on the systems which are thoroughly planned in the design phase.

    Also as I said, articles on this blog cover only specific problems, they do not extend to solving “big picture” issues since “big picture” issues will vary depending on your systems’ setup. LDAP is flexible enough that you can safely say there are no rules on how you will set up your own service, there are just general outlines.

    E-mail notifications from this blog are not meant to work, except the ones going through Jetpack (and those are beyond my control, I think they are used only for e-mail subscribers for new articles). I’m not some spammer. :-) Report me if/when they start working, since they really shouldn’t. :-)

Leave a Reply