A Clojure library to authenticate with LDAP

We have recently released as open source a small Clojure library that allows easy authentication users against an LDAP server:

https://github.com/realestate-com-au/clj-ldap-auth

It uses the UnboundID LDAP SDK for Java to look up a user name in an LDAP server and attempt to bind with specified credentials.

The simplest usage looks like:

(require '[clj-ldap-auth.ldap :as ldap])

(if (ldap/bind? username password)
 (do something-great)
 (unauthorised))

That works, but isn’t very helpful when authentication fails. So you can also pass a function that will be called with a diagnostic message in the event that authentication fails:

(let [reason (atom nil)]
  (if (ldap/bind? username password #(reset! reason %1))
    (do something-great)
    (unauthorised @reason)))

The provided function should take a single argument, which will be a string.

Configuration of the library (i.e. the ldap server to connect to, etc.) is via system properties. See the README for details.

Implementation

The library first establishes a connection to the server, optionally using SSL. If a bind-dn is configured (i.e. credentials with which to connect to the LDAP server), it is used to bind to the server. If that’s successful, we then look up the provided username (in the attribute uid). If found, the entry’s distinguished name (DN) is extracted and this DN and the provided password are used to bind a new connection.

If any of these steps fail (e.g. the binddn is unauthorised, the username can’t be found, or the looked up DN and password can’t bind) the function returns false (and calls the provided sink function to say why). If everything works and the connection can be bound with the target DN and password, it returns true (and the sink function is not called).

Limitations

It would probably be useful to be able to specify what attribute(s) to use for looking up the username, but for now it is hard coded to uid. Also, current test coverage (using midje) is minimal. UnboundID provide an in-memory LDAP server implementation, which could probably be used to build some fast-running integration tests.

This entry was posted in Engineering, REA Innovation by Michael Rowe. Bookmark the permalink.

About Michael Rowe

Michael is a Tech Lead in REA's Global Infrastructure and Architecture group. By day, he helps teams build and integrate their apps. At other times, he's likely to be found riding a bike in the nearby hills or spending time with his three children (none of whom seem to be computer nerds... so far).
  • Ken Scambler

    Hey Mike,

    It doesn’t seem very Clojurey to inflict a side-effect on your users for something so trivial as an error signal!

    Have you considered a more idiomatic functional approach, like returning a structure like {:ok “No worries”} or {:error “Server borked”}?

    It’s easy to handle, and you could still put a booleany function on top:

    (def bind? [username pwd]

    (:ok (ldap/bind username pwd)))

    (if-let [{error :error} (ldap/bind “me” “pwd”)]

    (unauthorized error))

    You could also take the decision inside, by providing 2 lambdas:

    (def pure-result

    (ldap/bind “me” “pwd”

    #(do something great)
    #(unauthorised %)))

    If you don’t like the nullary success lambda, you could simulate non-strict evaluation with a macro.

    That way you only have to bust out the concurrency primitives when you really need them. 🙂

    EDIT: erk… formatting. Anyway you get the picture.

    • I did think about that. I guess a combination of (a) wanting the function to have a simple predicate interface, and (b) bind? having implicit side effects anyway led me to prefer this way. *shrug*

      Premature purity is the root of all evil? 😉

    • Ken Scambler

      I would argue the atom shenanigans should put to rest any arguments about “simplicity” in the interface.

      Philosophically, I’m inclined to say that deferring side-effects until you really need them makes far more sense than deferring purity until you really need it, so “premature purity” is not really a thing, IMHO.

      Are you really getting the best out of Clojure if you’re already busting out side-effects before you get to the interesting stuff like asynchrony, concurrency, network calls, etc? Clojure deliberately makes you wear helmets and fluoro vests for this kind of stuff so that it’s not the default.

      My 2c anyway. YMMV and all that. 🙂