LDAP authentication

Ktor supports LDAP (Lightweight Directory Access Protocol) for credential authentication.

  1. authentication {
  2. basic("authName") {
  3. realm = "realm"
  4. validate { credential ->
  5. ldapAuthenticate(credential, "ldap://$localhost:${ldapServer.port}", "uid=%s,ou=system")
  6. }
  7. }
  8. }

Optionally you can define an additional validation check:

  1. authentication {
  2. basic("authName") {
  3. realm = "realm"
  4. validate { credential ->
  5. ldapAuthenticate(credentials, "ldap://localhost:389", "cn=%s ou=users") {
  6. if (it.name == it.password) {
  7. UserIdPrincipal(it.name)
  8. } else {
  9. null
  10. }
  11. }
  12. }
  13. }
  14. }

This signature looks like this:

  1. // Simplified signatures
  2. fun ldapAuthenticate(credential: UserPasswordCredential, ldapServerURL: String, userDNFormat: String): UserIdPrincipal?
  3. fun ldapAuthenticate(credential: UserPasswordCredential, ldapServerURL: String, userDNFormat: String, validate: InitialDirContext.(UserPasswordCredential) -> UserIdPrincipal?): UserIdPrincipal?

To support more complex scenarios, there is a more complete signature for ldapAuthenticate:

  1. fun <K : Credential, P : Any> ldapAuthenticate(credential: K, ldapServerURL: String, ldapEnvironmentBuilder: (MutableMap<String, Any?>) -> Unit = {}, doVerify: InitialDirContext.(K) -> P?): P?

While the other overloads support only UserPasswordCredential, this overload accept any kind of credential. And instead of receiving a string with the userDNFormat, you can provide a generatorto populate a map with the environments for ldap.

A more advanced example using this:

  1. application.install(Authentication) {
  2. basic {
  3. validate { credential ->
  4. ldapAuthenticate(
  5. credential,
  6. "ldap://$localhost:${ldapServer.port}",
  7. configure = { env: MutableMap<String, Any?> ->
  8. env.put("java.naming.security.principal", "uid=admin,ou=system")
  9. env.put("java.naming.security.credentials", "secret")
  10. env.put("java.naming.security.authentication", "simple")
  11. }
  12. ) {
  13. val users = (lookup("ou=system") as LdapContext).lookup("ou=users") as LdapContext
  14. val controls = SearchControls().apply {
  15. searchScope = SearchControls.ONELEVEL_SCOPE
  16. returningAttributes = arrayOf("+", "*")
  17. }
  18. users.search("", "(uid=user-test)", controls).asSequence().firstOrNull {
  19. val ldapPassword = (it.attributes.get("userPassword")?.get() as ByteArray?)?.toString(Charsets.ISO_8859_1)
  20. ldapPassword == credential.password
  21. }?.let { UserIdPrincipal(credential.name) }
  22. }
  23. }
  24. }
  25. }

You can see advanced examples for LDAP authentication in the Ktor’s tests.

This feature is defined in the package io.ktor.auth.ldap in the artifact io.ktor:ktor-auth-ldap:$ktor_version.

dependencies { implementation "io.ktor:ktor-auth-ldap:$ktor_version"}

dependencies { implementation("io.ktor:ktor-auth-ldap:$ktor_version")}

<project> … <dependencies> <dependency> <groupId>io.ktor</groupId> <artifactId>ktor-auth-ldap</artifactId> <version>${ktor.version}</version> <scope>compile</scope> </dependency> </dependencies></project>

Bear in mind that current LDAP implementation is synchronous.