Docs
Plugin HubAuthenticationLDAP Auth AdvancedOverview

ldap-auth-advanced

The ldap-auth-advanced plugin authenticates clients against an LDAP directory, such as OpenLDAP or Active Directory, and optionally maps the authenticated directory user onto a consumer.

The plugin resolves users with a search-then-bind flow. It searches base_dn for an entry whose attribute matches the client-supplied user name. It then binds as that entry with the client-supplied password. Because the user's distinguished name (DN) is discovered rather than constructed, users can live at different depths without encoding the directory layout in gateway configuration.

When consumer_required is enabled, which is the default, the plugin looks for a consumer whose credential records the resolved DN. On a match, the gateway adds headers such as X-Consumer-Username and X-Credential-Identifier before proxying the request upstream. Per-consumer plugins, rate limits, and analytics can then apply to LDAP-authenticated traffic. Setting consumer_required to false authenticates against the directory without requiring a consumer.

Available in API7 Enterprise from version 3.10.5 and in APISIX from version 3.18.0.

info

The plugin first tries Proxy-Authorization when that header contains credentials for the configured scheme, then falls back to Authorization. The scheme word is determined by header_type: with the default ldap, clients send Authorization: ldap <base64(username:password)>; with basic, they send an ordinary Authorization: Basic <base64(username:password)> header, which lets existing HTTP Basic clients authenticate unchanged.

The plugin returns 401 Unauthorized for missing, malformed, or rejected user credentials, ambiguous user matches, and a missing consumer when consumer_required is enabled. LDAP transport, TLS, protocol, server, search-bind, and other directory failures return 500 Internal Server Error so an outage is not presented as an invalid user credential.

Examples

The examples use an isolated OpenLDAP directory with two test users. The gateway searches the directory as cn=admin,dc=example,dc=org and removes the credential header before proxying authenticated requests upstream.

Set Up OpenLDAP

Start the directory in the same environment as the gateway.

Create users.ldif to add the test users:

users.ldif
dn: ou=users,dc=example,dc=org
objectClass: organizationalUnit
ou: users

dn: uid=johndoe,ou=users,dc=example,dc=org
objectClass: inetOrgPerson
cn: John Doe
sn: Doe
uid: johndoe
mail: johndoe@example.org
userPassword: john-secret

dn: uid=janedoe,ou=users,dc=example,dc=org
objectClass: inetOrgPerson
cn: Jane Doe
sn: Doe
uid: janedoe
mail: janedoe@example.org
userPassword: jane-secret

Generate a short-lived CA and server certificate for the TLS examples:

mkdir -p ldap-certs

openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout ldap-certs/ca.key \
  -out ldap-certs/ca.crt \
  -sha256 -days 30 \
  -subj "/CN=LDAP Docs CA"

openssl req -newkey rsa:2048 -nodes \
  -keyout ldap-certs/server.key \
  -out ldap-certs/server.csr \
  -subj "/CN=openldap"

Create the certificate extensions file:

ldap-certs/server.ext
subjectAltName=DNS:openldap
extendedKeyUsage=serverAuth

Sign the server certificate and make the local evaluation key readable by the containerized LDAP process:

openssl x509 -req \
  -in ldap-certs/server.csr \
  -CA ldap-certs/ca.crt \
  -CAkey ldap-certs/ca.key \
  -CAcreateserial \
  -out ldap-certs/server.crt \
  -days 30 -sha256 \
  -extfile ldap-certs/server.ext

chmod 644 ldap-certs/server.key

Set GATEWAY_CONTAINER to the running APISIX or API7 Gateway container. Create a dedicated network and connect the gateway to it:

export GATEWAY_CONTAINER=replace-with-gateway-container-name

docker network create gateway-ldap-net
docker network connect gateway-ldap-net "$GATEWAY_CONTAINER"

Start OpenLDAP 2.6 LTS with LDAP, StartTLS, and LDAPS enabled:

docker run -d \
  --name openldap \
  --hostname openldap \
  --network gateway-ldap-net \
  -e LDAP_DOMAIN=example.org \
  -e LDAP_BASE_DN=dc=example,dc=org \
  -e 'LDAP_ORGANISATION=Example Organization' \
  -e LDAP_ADMIN_PASSWORD=admin-secret \
  -e LDAP_CREATE_PEOPLE_OU=false \
  -e LDAP_CREATE_GROUPS_OU=false \
  -e LDAP_ENABLE_TLS=true \
  -e LDAP_ENABLE_LDAPS=true \
  -e LDAP_TLS_CERT_FILE=/etc/openldap/certs/server.crt \
  -e LDAP_TLS_KEY_FILE=/etc/openldap/certs/server.key \
  -e LDAP_TLS_CA_FILE=/etc/openldap/certs/ca.crt \
  -v "$PWD/users.ldif:/docker-entrypoint-initdb.d/20-users.ldif:ro" \
  -v "$PWD/ldap-certs:/etc/openldap/certs:ro" \
  chrroessner/openldap:2.6.15-debian13.7

Wait until the container is healthy, then verify that John can bind and be found:

docker inspect --format '{{.State.Health.Status}}' openldap

docker exec openldap ldapsearch -x \
  -H ldap://127.0.0.1:389 \
  -D 'cn=admin,dc=example,dc=org' \
  -w admin-secret \
  -b 'ou=users,dc=example,dc=org' \
  '(uid=johndoe)' dn uid

The fixture uses tutorial credentials and stores its directory in the container filesystem. For production, use persistent storage, Kubernetes Secrets or another credential manager, restricted network access, and TLS with a trusted certificate.

Authenticate Against an LDAP Directory

This example authenticates clients against the directory without mapping them onto consumers.

Create a route with ldap-auth-advanced:

curl "http://127.0.0.1:9180/apisix/admin/routes/ldap-auth-route" -X PUT \
  -H "X-API-KEY: ${ADMIN_API_KEY}" \
  -d '{
    "uri": "/ldap-auth",
    "plugins": {
      "proxy-rewrite": {
        "uri": "/anything",
        "headers": {
          "remove": ["Authorization", "Proxy-Authorization"]
        }
      },
      "ldap-auth-advanced": {
        "ldap_uri": "openldap:389",
        "base_dn": "ou=users,dc=example,dc=org",
        "attribute": "uid",
        "bind_dn": "cn=admin,dc=example,dc=org",
        "ldap_password": "admin-secret",
        "consumer_required": false
      }
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Verify with Valid Credentials

Send a request to the route with the directory user's credentials, base64 encoded and presented in the ldap scheme:

curl -i "http://127.0.0.1:9080/ldap-auth" \
  -H "Authorization: ldap $(printf '%s' 'johndoe:john-secret' | base64)"

The request should return 200 OK. The upstream response should not contain Authorization or Proxy-Authorization.

Verify with Invalid Credentials

Send a request to the route with an incorrect password:

curl -i "http://127.0.0.1:9080/ldap-auth" \
  -H "Authorization: ldap $(printf '%s' 'johndoe:wrong-password' | base64)"

The request should return 401 Unauthorized with the LDAP challenge:

WWW-Authenticate: ldap realm="ldap"

The response body should be:

{"message":"Authorization required"}

Verify without Credentials

Send a request to the route without any credentials:

curl -i "http://127.0.0.1:9080/ldap-auth"

The request should return 401 Unauthorized.

Map LDAP Users to Consumers

This example maps a directory user onto a consumer so consumer-scoped plugins, rate limits, and analytics apply to their traffic. The consumer credential records the full DN resolved by the directory search.

info

Consumer credentials of type ldap-auth-advanced are created through the Admin API or the Dashboard. ADC and the Ingress Controller currently support only key-auth, basic-auth, jwt-auth, and hmac-auth credentials.

Create a consumer johndoe:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
  -H "X-API-KEY: ${ADMIN_API_KEY}" \
  -d '{
    "username": "johndoe"
  }'

Create an ldap-auth-advanced credential for the consumer, recording the DN of the directory entry:

curl "http://127.0.0.1:9180/apisix/admin/consumers/johndoe/credentials" -X PUT \
  -H "X-API-KEY: ${ADMIN_API_KEY}" \
  -d '{
    "id": "cred-john-ldap-auth",
    "plugins": {
      "ldap-auth-advanced": {
        "user_dn": "uid=johndoe,ou=users,dc=example,dc=org"
      }
    }
  }'

Create a route with ldap-auth-advanced, leaving consumer_required at its default of true:

curl "http://127.0.0.1:9180/apisix/admin/routes/ldap-auth-consumer-route" -X PUT \
  -H "X-API-KEY: ${ADMIN_API_KEY}" \
  -d '{
    "uri": "/ldap-consumer",
    "plugins": {
      "proxy-rewrite": {
        "uri": "/anything",
        "headers": {
          "remove": ["Authorization", "Proxy-Authorization"]
        }
      },
      "ldap-auth-advanced": {
        "ldap_uri": "openldap:389",
        "base_dn": "ou=users,dc=example,dc=org",
        "attribute": "uid",
        "bind_dn": "cn=admin,dc=example,dc=org",
        "ldap_password": "admin-secret"
      }
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Send a request to the route with the directory user's credentials:

curl -i "http://127.0.0.1:9080/ldap-consumer" \
  -H "Authorization: ldap $(printf '%s' 'johndoe:john-secret' | base64)"

The request should return 200 OK. The upstream should receive the consumer identity without either credential header:

{
  "headers": {
    "X-Consumer-Username": "johndoe",
    "X-Credential-Identifier": "cred-john-ldap-auth"
  }
}

Jane exists in the directory but has no consumer credential. Send her valid directory credentials:

curl -i "http://127.0.0.1:9080/ldap-consumer" \
  -H "Authorization: ldap $(printf '%s' 'janedoe:jane-secret' | base64)"

The request should return 401 Unauthorized, confirming that successful directory authentication does not bypass the consumer requirement.

Accept the HTTP Basic Authentication Scheme

This example accepts the standard HTTP Basic scheme so existing Basic clients can authenticate without changing their credential format.

Set header_type to basic on the route:

curl "http://127.0.0.1:9180/apisix/admin/routes/ldap-auth-basic-route" -X PUT \
  -H "X-API-KEY: ${ADMIN_API_KEY}" \
  -d '{
    "uri": "/ldap-basic",
    "plugins": {
      "proxy-rewrite": {
        "uri": "/anything",
        "headers": {
          "remove": ["Authorization", "Proxy-Authorization"]
        }
      },
      "ldap-auth-advanced": {
        "ldap_uri": "openldap:389",
        "base_dn": "ou=users,dc=example,dc=org",
        "attribute": "uid",
        "bind_dn": "cn=admin,dc=example,dc=org",
        "ldap_password": "admin-secret",
        "header_type": "basic",
        "consumer_required": false
      }
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Send a request using an ordinary Basic credential:

curl -i "http://127.0.0.1:9080/ldap-basic" -u johndoe:john-secret

The request should return 200 OK, and the upstream should not receive the Authorization header.

Send a request without credentials:

curl -i "http://127.0.0.1:9080/ldap-basic"

The request should return 401 Unauthorized with the Basic challenge:

WWW-Authenticate: Basic realm="ldap"

Connect to the Directory Over TLS

The plugin supports LDAPS and StartTLS. The Docker fixture above enables both transports, while the Kubernetes fixture exposes plaintext LDAP only. The remainder of this section uses the Docker fixture. For Kubernetes, configure the directory with a certificate Secret and expose its TLS listener before adapting this example. The two plugin options are mutually exclusive:

TransportDirectory addressConfiguration
LDAPSopenldap:636use_ldaps: true
StartTLSopenldap:389use_starttls: true

Copy the gateway container's system CA bundle, append the directory's issuing CA, and copy the combined bundle back into the container:

docker cp \
  "$GATEWAY_CONTAINER":/etc/ssl/certs/ca-certificates.crt \
  ldap-certs/system-ca-bundle.crt

cat ldap-certs/system-ca-bundle.crt ldap-certs/ca.crt \
  > ldap-certs/gateway-ca-bundle.crt

docker cp ldap-certs/gateway-ca-bundle.crt \
  "$GATEWAY_CONTAINER":/usr/local/apisix/conf/gateway-ca-bundle.crt

Configure the gateway to use the combined bundle in config.yaml:

config.yaml
apisix:
  ssl:
    ssl_trusted_certificate: /usr/local/apisix/conf/gateway-ca-bundle.crt

Reload the gateway after saving the configuration:

docker exec "$GATEWAY_CONTAINER" apisix reload

The trust bundle is global. Preserve the deployment's existing CA certificates and apply the configuration and bundle to every gateway instance in a multi-instance deployment.

Create an LDAPS-protected route. Certificate verification remains enabled:

curl "http://127.0.0.1:9180/apisix/admin/routes/ldap-auth-ldaps-route" -X PUT \
  -H "X-API-KEY: ${ADMIN_API_KEY}" \
  -d '{
    "uri": "/ldap-ldaps",
    "plugins": {
      "proxy-rewrite": {
        "uri": "/anything",
        "headers": {
          "remove": ["Authorization", "Proxy-Authorization"]
        }
      },
      "ldap-auth-advanced": {
        "ldap_uri": "openldap:636",
        "use_ldaps": true,
        "ssl_verify": true,
        "base_dn": "ou=users,dc=example,dc=org",
        "attribute": "uid",
        "bind_dn": "cn=admin,dc=example,dc=org",
        "ldap_password": "admin-secret",
        "consumer_required": false
      }
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Send an authenticated request through LDAPS:

curl -i "http://127.0.0.1:9080/ldap-ldaps" \
  -H "Authorization: ldap $(printf '%s' 'johndoe:john-secret' | base64)"

The request should return 200 OK, and the upstream should not receive the credential header. The same fixture also supports StartTLS with the values shown in the table.