Working with LDAP/Active Directory authentication can be frustrating. When credentials fail, you’re often left with cryptic error codes and little guidance on what went wrong. To make this easier, I built a simple PHP command-line tool that lets you test LDAP connections and user authentication with detailed diagnostics.
Whether you’re troubleshooting an intranet login, integrating LDAP into a PHP application, or just verifying that your Active Directory setup is working, this tool gives you a clear picture of what’s happening behind the scenes.
🚀 Why This Tool?
LDAP is powerful but notoriously tricky to debug. Typical problems include:
- Wrong username format (UPN vs. DN vs.
DOMAIN\username
) - Invalid credentials (
error 49
) - No such object (
error 32
) - TLS/SSL certificate issues
- Firewall or network problems
This tool aims to diagnose each step of the process:
- Test basic network connectivity
- Check if the LDAP server responds
- Attempt anonymous and authenticated binds
- Show supported LDAP versions and SASL mechanisms
- Suggest alternative username formats if authentication fails
⚙️ Requirements
- PHP with LDAP extension (
php-ldap
) - Network access to your LDAP/AD server
Install the LDAP extension if you don’t have it:
# Ubuntu/Debian
sudo apt-get install php-ldap
# CentOS/RHEL
sudo yum install php-ldap
Script
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Function to read password securely (hidden input)
function readPasswordHidden($prompt = "Password: ") {
echo $prompt;
// Check if we're on Windows or Unix-like system
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
// Windows - use PowerShell for hidden input
$password = rtrim(shell_exec('powershell -Command "$p = Read-Host -AsSecureString; [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($p))"'));
} else {
// Unix/Linux/Mac - use stty to hide input
system('stty -echo');
$password = rtrim(fgets(STDIN));
system('stty echo');
echo "\n"; // Add newline since it was hidden
}
return $password;
}
// Check command line arguments
if ($argc < 2) {
echo "Usage: php {$argv[0]} <ldap-dn> [ldap-host]\n";
echo "Examples:\n";
echo " php {$argv[0]} \"CN=Administrator,CN=Users,DC=example,DC=local\"\n";
echo " php {$argv[0]} \"Administrator@example.local\" \"ldap://192.168.1.100:389\"\n";
echo " php {$argv[0]} \"DOMAIN\\\\username\"\n";
exit(1);
}
$ldap_dn = $argv[1];
$ldap_host = isset($argv[2]) ? $argv[2] : "ldap://ip-or-domain:port";
// Read password securely
$ldap_pass = readPasswordHidden("Enter password for '$ldap_dn': ");
if (empty($ldap_pass)) {
die("❌ Password cannot be empty!\n");
}
echo "\n🔍 LDAP Authentication Test (Enhanced Debug)\n";
echo "============================================\n";
echo "Host: $ldap_host\n";
echo "Bind DN: $ldap_dn\n";
echo "Password: " . str_repeat('*', strlen($ldap_pass)) . " (" . strlen($ldap_pass) . " chars)\n\n";
// Check if LDAP extension is loaded
if (!extension_loaded('ldap')) {
die("❌ LDAP extension is not loaded!\n");
}
echo "✅ LDAP extension is loaded\n";
// Test basic connectivity first
echo "\n➡ Testing basic network connectivity...\n";
$host_parts = parse_url($ldap_host);
$host_ip = $host_parts['host'];
$host_port = isset($host_parts['port']) ? $host_parts['port'] : 389;
echo " Attempting to connect to $host_ip:$host_port...\n";
$socket = @fsockopen($host_ip, $host_port, $errno, $errstr, 5);
if ($socket) {
echo "✅ Network connection successful\n";
fclose($socket);
} else {
echo "❌ Network connection failed: $errstr ($errno)\n";
echo " Check if LDAP server is running and accessible\n";
exit(1);
}
// Optional: disable TLS cert checks for self-signed (testing only)
putenv('LDAPTLS_REQCERT=never');
ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
// Connect with more detailed error handling
echo "\n➡ Creating LDAP connection...\n";
$ldap_conn = ldap_connect($ldap_host);
if (!$ldap_conn) {
die("❌ Could not create LDAP connection handle\n");
} else {
echo "✅ LDAP connection resource created\n";
}
// Set options before binding
echo "➡ Setting LDAP options...\n";
if (!ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, 3)) {
echo "⚠ Failed to set LDAP protocol version to 3\n";
}
if (!ldap_set_option($ldap_conn, LDAP_OPT_REFERRALS, 0)) {
echo "⚠ Failed to disable referrals\n";
}
// Set network timeout
ldap_set_option($ldap_conn, LDAP_OPT_NETWORK_TIMEOUT, 10);
// Dump current options for debugging
$version = null;
$referrals = null;
$timeout = null;
ldap_get_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, $version);
ldap_get_option($ldap_conn, LDAP_OPT_REFERRALS, $referrals);
ldap_get_option($ldap_conn, LDAP_OPT_NETWORK_TIMEOUT, $timeout);
echo "ℹ LDAP protocol version: $version\n";
echo "ℹ Referrals enabled: " . ($referrals ? "Yes" : "No") . "\n";
echo "ℹ Network timeout: {$timeout}s\n\n";
// Try anonymous bind first to test basic LDAP connectivity
echo "➡ Testing anonymous bind...\n";
if (@ldap_bind($ldap_conn)) {
echo "✅ Anonymous bind successful - LDAP server is responding\n";
// Try to read RootDSE with anonymous access
echo "➡ Reading RootDSE with anonymous access...\n";
$sr = @ldap_read($ldap_conn, "", "(objectClass=*)", ["supportedLDAPVersion", "defaultNamingContext", "supportedSASLMechanisms"]);
if ($sr) {
$entries = ldap_get_entries($ldap_conn, $sr);
if ($entries['count'] > 0) {
echo "✅ RootDSE accessible:\n";
if (isset($entries[0]["supportedldapversion"])) {
echo " Supported LDAP versions: " . implode(", ", array_slice($entries[0]["supportedldapversion"], 1)) . "\n";
}
if (isset($entries[0]["defaultnamingcontext"][0])) {
echo " Default naming context: " . $entries[0]["defaultnamingcontext"][0] . "\n";
}
if (isset($entries[0]["supportedsaslmechanisms"])) {
echo " Supported SASL mechanisms: " . implode(", ", array_slice($entries[0]["supportedsaslmechanisms"], 1)) . "\n";
}
}
}
} else {
echo "⚠ Anonymous bind failed: " . ldap_error($ldap_conn) . "\n";
echo " This might be expected if anonymous access is disabled\n";
}
// Attempt authenticated bind
echo "\n➡ Attempting authenticated bind...\n";
echo " DN: $ldap_dn\n";
echo " Password length: " . strlen($ldap_pass) . " characters\n";
// Test with provided credentials
$bind_result = @ldap_bind($ldap_conn, $ldap_dn, $ldap_pass);
if ($bind_result) {
echo "✅ Authentication successful!\n";
// Test a simple search to verify the connection works
echo "➡ Testing search functionality...\n";
$search_base = "CN=Users,DC=example,DC=local";
$search_result = @ldap_search($ldap_conn, $search_base, "(objectClass=user)", ["cn", "sAMAccountName"], 0, 5);
if ($search_result) {
$entries = ldap_get_entries($ldap_conn, $search_result);
echo "✅ Search successful - found {$entries['count']} user(s)\n";
// Display some user information if found
if ($entries['count'] > 0) {
echo " Sample users found:\n";
for ($i = 0; $i < min(3, $entries['count']); $i++) {
$cn = isset($entries[$i]['cn'][0]) ? $entries[$i]['cn'][0] : 'N/A';
$sam = isset($entries[$i]['samaccountname'][0]) ? $entries[$i]['samaccountname'][0] : 'N/A';
echo " - CN: $cn, sAMAccountName: $sam\n";
}
}
} else {
echo "⚠ Search failed: " . ldap_error($ldap_conn) . "\n";
echo " This might be due to insufficient permissions or incorrect base DN\n";
}
} else {
$err_no = ldap_errno($ldap_conn);
$err_msg = ldap_error($ldap_conn);
echo "❌ Authentication failed!\n";
echo " LDAP Error Code: $err_no\n";
echo " LDAP Error Message: $err_msg\n";
// Try to get more detailed diagnostic information
$diag_msg = null;
if (ldap_get_option($ldap_conn, LDAP_OPT_DIAGNOSTIC_MESSAGE, $diag_msg) && !empty($diag_msg)) {
echo " Diagnostic Message: $diag_msg\n";
}
echo "\n🔧 Troubleshooting suggestions:\n";
// Common error codes and suggestions
switch ($err_no) {
case 49: // LDAP_INVALID_CREDENTIALS
echo " - Invalid credentials (error 49)\n";
echo " → Verify username and password\n";
echo " → Check if account is locked or disabled\n";
echo " → Verify DN format matches your AD structure\n";
echo " → Try with sAMAccountName format: Administrator\n";
break;
case 32: // LDAP_NO_SUCH_OBJECT
echo " - Object not found (error 32)\n";
echo " → Verify the DN path exists\n";
echo " → Check your domain components (DC=example,DC=local)\n";
break;
case 34: // LDAP_INVALID_DN_SYNTAX
echo " - Invalid DN syntax (error 34)\n";
echo " → Check DN format and escaping\n";
break;
case -1: // Connection issues
echo " - Connection problem (error -1)\n";
echo " → Check network connectivity\n";
echo " → Verify LDAP server is running\n";
echo " → Check firewall settings\n";
break;
default:
echo " - See LDAP error code documentation for error $err_no\n";
}
echo "\n🔧 Alternative authentication formats to consider:\n";
echo " 1. Distinguished Name: CN=username,CN=Users,DC=domain,DC=com\n";
echo " 2. sAMAccountName: username\n";
echo " 3. User Principal Name: username@domain.com\n";
echo " 4. Legacy format: DOMAIN\\username\n";
// Extract potential alternative formats from the provided DN
$alternative_dns = [];
// If it looks like a DN, try extracting sAMAccountName-like format
if (strpos($ldap_dn, 'CN=') !== false) {
preg_match('/CN=([^,]+)/', $ldap_dn, $matches);
if (isset($matches[1])) {
$alternative_dns[] = $matches[1]; // Just the username
}
}
// If it has domain info, try other formats
if (strpos($ldap_dn, '@') !== false) {
$parts = explode('@', $ldap_dn);
if (count($parts) == 2) {
$alternative_dns[] = $parts[0]; // Just username without domain
$domain_upper = strtoupper(explode('.', $parts[1])[0]);
$alternative_dns[] = $domain_upper . '\\' . $parts[0]; // DOMAIN\username
}
}
if (!empty($alternative_dns)) {
echo "\n➡ Suggested alternative formats based on your input:\n";
foreach (array_unique($alternative_dns) as $alt_dn) {
if ($alt_dn !== $ldap_dn) {
echo " - Try: php {$argv[0]} \"$alt_dn\"\n";
}
}
}
}
// Clear password from memory for security
$ldap_pass = str_repeat('0', strlen($ldap_pass));
unset($ldap_pass);
ldap_unbind($ldap_conn);
echo "\n✅ Test complete.\n";
?>
💻 Usage
Save the script as ldap-test.php
, then run:
php ldap-test.php "<username>" [ldap-host]
You’ll be prompted for a password (securely hidden input).
Common Examples
# Active Directory - User Principal Name
php ldap-test.php "admin@company.com"
# Distinguished Name
php ldap-test.php "CN=Administrator,CN=Users,DC=company,DC=local"
# Simple username
php ldap-test.php "administrator"
# Legacy format
php ldap-test.php "COMPANY\\admin"
# Custom server
php ldap-test.php "admin@company.com" "ldap://192.168.1.100:389"
# Secure LDAP (LDAPS)
php ldap-test.php "admin@company.com" "ldaps://dc01.company.com:636"
🔍 What It Does
Here’s what happens when you run the tool:
- Connectivity Check – Verifies the server is reachable on the given host/port.
- LDAP Extension Check – Ensures PHP has LDAP support.
- Anonymous Bind Test – Confirms if the server allows basic queries.
- RootDSE Read – Retrieves server info (supported LDAP versions, naming context).
- Authenticated Bind – Attempts login with your credentials.
- Search Test – Runs a simple search to validate permissions.
- Error Diagnostics – If authentication fails, shows error code, message, and troubleshooting tips.
🧰 Common Errors & Fixes
Error | Meaning | Fix |
---|---|---|
49 | Invalid credentials | Try different username format, verify password, check if account is locked/disabled |
32 | No such object | Verify DN path (CN=Users,DC=example,DC=local ) |
34 | Invalid DN syntax | Fix your DN formatting |
-1 | Connection issue | Check firewall, server status, network reachability |
If authentication fails, the tool also suggests alternative formats automatically, such as:
username@domain.com
DOMAIN\username
CN=username,CN=Users,DC=domain,DC=com
- Just
username
📝 Example Output
🔍 LDAP Authentication Test (Enhanced Debug)
============================================
Host: ldap://192.168.1.100:389
Bind DN: admin@company.com
Password: ******** (8 chars)
✅ LDAP extension is loaded
✅ Network connection successful
✅ LDAP connection resource created
ℹ LDAP protocol version: 3
ℹ Referrals enabled: No
ℹ Network timeout: 10s
➡ Attempting authenticated bind...
✅ Authentication successful!
✅ Search successful - found 3 user(s)
- CN: Administrator, sAMAccountName: administrator
If something goes wrong, you’ll get detailed output like:
❌ Authentication failed!
LDAP Error Code: 49
LDAP Error Message: Invalid credentials
Diagnostic Message: 80090308: LdapErr: DSID-0C0903AA, comment: AcceptSecurityContext error
🔧 Troubleshooting suggestions:
- Verify username and password
- Check account lockout
- Try alternative login formats (DOMAIN\user, user@domain.com)
✅ Conclusion
This LDAP Authentication Test Tool helps take the guesswork out of LDAP/Active Directory troubleshooting. Instead of chasing vague errors, you get:
- Clear diagnostics
- Alternative login format suggestions
- Confidence that your PHP-LDAP setup is working
If you’re integrating LDAP into PHP applications or just need a quick way to validate credentials, this script can save you hours of debugging.
Top comments (0)