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)