Skip to content

LDAP Authentication using Java

Authenticating a user against an LDAP directory does not require a framework. A plain JNDI context is enough: bind to the directory as the user with the password they supplied and let the directory server decide whether the credentials are valid.

This post was written in 2008. The JNDI API shown here is still part of the JDK and still works, but the original example connected over an unencrypted ldap:// URL, which sends the password over the network in clear text. The code below has been corrected to use ldaps:// and to close the context again.

The Example

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

import java.util.Hashtable;

/**
 * Authenticates a user against an LDAP directory by performing a simple bind.
 */
public final class LdapAuthenticator {

    private static final String LDAP_URL = "ldaps://ldap.example.com:636";
    private static final String USER_BASE = "ou=People,dc=example,dc=com";

    public static boolean authenticate(String userName, String password) throws NamingException {
        // Many directories answer a bind with an empty password with an
        // anonymous bind, which succeeds. Reject it before asking the server.
        if (password == null || password.isEmpty()) {
            return false;
        }

        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, LDAP_URL);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, "uid=" + userName + "," + USER_BASE);
        env.put(Context.SECURITY_CREDENTIALS, password);

        DirContext context = null;
        try {
            context = new InitialDirContext(env);
            return true;
        } catch (AuthenticationException e) {
            return false;
        } finally {
            if (context != null) {
                context.close();
            }
        }
    }

    public static void main(String[] args) throws NamingException {
        boolean authenticated = authenticate(args[0], args[1]);
        System.out.println(authenticated ? "Authentication success" : "Authentication failed");
    }
}

What Is Going On Here

  • Context.SECURITY_AUTHENTICATION set to simple means the password is sent to the server in a plain bind request. That is precisely why the transport must be encrypted, either with ldaps:// or by negotiating StartTLS on port 389.
  • Context.SECURITY_PRINCIPAL expects the full distinguished name (DN) of the entry, not the login name. If the DN cannot be constructed from the login name in your directory, bind with a service account first, search for the user entry and then bind again with the DN you found.
  • A successful new InitialDirContext(...) means the credentials were accepted, and an AuthenticationException means they were not. Every other NamingException is an infrastructure problem — an unreachable server, a TLS error, a wrong base DN — and should be logged rather than reported to the user as a failed login.
  • Each successful bind opens a connection to the directory. Close the context when you are done, otherwise a busy login page will exhaust the server’s connection pool.