Notice: Due to size constraints and loading performance considerations, scripts referenced in blog posts are not attached directly. To request access, please complete the following form: Script Request Form Note: A Google account is required to access the form.
Disclaimer: I do not accept responsibility for any issues arising from scripts being run without adequate understanding. It is the user's responsibility to review and assess any code before execution. More information

When Users Are Right and Your Code Is Wrong: An LDAP Authentication Mystery

I recently encountered a frustrating issue with a password reset application I had developed. Users were reporting that they couldn't reset their password even when they were absolutely certain their passwords were correct. The logs were filled with authentication failures, and I was starting to question what was going on - was it actually the user making a mistake or was my code not functioning properly?

Here's what I was seeing in the logs:

2025-06-27 10:15:59 - Attempting to authenticate user: admjdoe1@bear.local against domain: bear.local
2025-06-27 10:15:59 - LDAP authentication failed: The user name or password is incorrect.
2025-06-27 10:15:59 - Attempting to authenticate user: bear.local\admjdoe1 against domain: bear.local
2025-06-27 10:15:59 - LDAP authentication failed: The user name or password is incorrect.
2025-06-27 10:15:59 - All authentication attempts failed for user: admjdoe1@bear.local

Users like Jane Doe were entering admjdoe1@bear.local as their username, confirming their passwords were correct using other systems, yet my script kept rejecting them. The frustrating part? This wasn't happening to all users—just some.

The “what the hell” moment

After digging deeper into Active Directory, I discovered the root cause. The issue wasn't with the users' passwords—it was case sensitivity.

When I checked Active Directory, I found that while the user was entering:

  • admjdoe1@bear.local

The actual account in AD was stored as:

  • ADMJDoe1@bear.local

The original script I had written was making a critical assumption: that the username entered by the user would exactly match the case-sensitive format stored in Active Directory.

Assumptions cause problems in code!

Here's what my original authentication method looked like:

private bool VerifyCurrentPassword(string userPrincipalName, string password)
{
    try
    {
        string username = userPrincipalName;
        if (userPrincipalName.Contains("@"))
        {
            username = userPrincipalName.Split('@')[0];
        }
        
        WriteToLog(string.Format("Attempting to authenticate user: {0} against domain: {1}", 
            username, Domain));
        
        string ldapPath = string.Format("LDAP://{0}", Domain);
        using (DirectoryEntry entry = new DirectoryEntry(ldapPath, username, password))
        {
            object obj = entry.NativeObject; // Force authentication
            return true;
        }
    }
    catch (Exception ex)
    {
        WriteToLog(string.Format("LDAP authentication failed: {0}", ex.Message));
        return false;
    }
}

This approach was fundamentally flawed because it assumed the user would enter their username in the exact case that Active Directory expected. When a user entered admjdoe1 but their account was actually ADMJDoe1, the authentication would fail.

Lookup First, Then Authenticate

The solution was to implement a two-step process:

  1. First, find the user in Active Directory using case-insensitive search
  2. Then, use the correctly-cased username for authentication

Here's the improved approach:

private bool VerifyCurrentPassword(string userPrincipalName, string password)
{
    try
    {
        // First, find the user in AD to get the correct case-sensitive username
        string correctUsername = FindUserInAD(userPrincipalName);
        if (string.IsNullOrEmpty(correctUsername))
        {
            WriteToLog(string.Format("User not found in Active Directory: {0}", userPrincipalName));
            return false;
        }
        
        WriteToLog(string.Format("Found user in AD. Correct username: {0}", correctUsername));
        
        // Now authenticate with the correct case
        string ldapPath = string.Format("LDAP://{0}", Domain);
        using (DirectoryEntry entry = new DirectoryEntry(ldapPath, correctUsername, password))
        {
            object obj = entry.NativeObject;
            WriteToLog(string.Format("Authentication successful for user: {0}", correctUsername));
            return true;
        }
    }
    catch (Exception ex)
    {
        WriteToLog(string.Format("Authentication failed: {0}", ex.Message));
        return false;
    }
}

The magic happens in the FindUserInAD helper method:

private string FindUserInAD(string userPrincipalName)
{
    try
    {
        string ldapPath = string.Format("LDAP://{0}", Domain);
        using (DirectoryEntry directoryEntry = new DirectoryEntry(ldapPath))
        {
            using (DirectorySearcher searcher = new DirectorySearcher(directoryEntry))
            {
                string username = userPrincipalName.Contains("@") ? 
                    userPrincipalName.Split('@')[0] : userPrincipalName;
                
                // Case-insensitive search filters
                List<string> searchFilters = new List<string>
                {
                    string.Format("(&(objectCategory=person)(objectClass=user)(sAMAccountName={0}))", username),
                    string.Format("(&(objectCategory=person)(objectClass=user)(userPrincipalName={0}))", userPrincipalName),
                    string.Format("(&(objectCategory=person)(objectClass=user)(mail={0}))", userPrincipalName)
                };
                
                foreach (string filter in searchFilters)
                {
                    searcher.Filter = filter;
                    searcher.SearchScope = SearchScope.Subtree;
                    searcher.PropertiesToLoad.AddRange(new string[] { 
                        "sAMAccountName", "userPrincipalName", "mail" 
                    });
                    
                    SearchResult result = searcher.FindOne();
                    if (result != null)
                    {
                        WriteToLog(string.Format("User found in AD using filter: {0}", filter));
                        
                        if (result.Properties["userPrincipalName"].Count > 0)
                        {
                            string foundUPN = result.Properties["userPrincipalName"][0].ToString();
                            WriteToLog(string.Format("Found UPN: {0}", foundUPN));
                            return foundUPN;
                        }
                    }
                }
            }
        }
        return null;
    }
    catch (Exception ex)
    {
        WriteToLog(string.Format("Error searching for user in AD: {0}", ex.Message));
        return null;
    }
}

Reviewing the Logs

After implementing this fix, the logs now show successful authentication:

2025-06-27 11:30:15 - Verifying current credentials for user 'admjdoe1@bear.local'
2025-06-27 11:30:15 - User found in AD using filter: (&(objectCategory=person)(objectClass=user)(sAMAccountName=admjdoe1))
2025-06-27 11:30:15 - Found UPN: ADMJDoe1@bear.local
2025-06-27 11:30:15 - Found user in AD. Correct username: ADMJDoe1@bear.local
2025-06-27 11:30:15 - Attempting to authenticate user: ADMJDoe1@bear.local against domain: bear.local
2025-06-27 11:30:15 - Authentication successful for user: ADMJDoe1@bear.local

Now the user enters admjdoe1@bear.local, the system finds ADMJDoe1@bear.local in Active Directory, and uses the correct case for authentication.

Is this my code? Well…..

Active Directory stores usernames in a specific case format, but LDAP searches are typically case-insensitive while LDAP binds (authentication) can be case-sensitive depending on your domain configuration. This creates a scenario where:

  • Searching for admjdoe1 will find ADMJDoe1
  • Authenticating as admjdoe1 might fail if the system expects ADMJDoe1 

This inconsistency caught me off guard because it's not immediately obvious—some operations work case-insensitively while others don't.

Did this fix the issue?

Yes, it did, the website immediately went from failing password resets to successfully resetting passwords, the main goal of the website in the first place....


Lessons learned

This experience taught me an important lesson about making assumptions in authentication systems. Just because a user "knows" their username doesn't mean they know the exact case format that the system expects.

Conclusions

The fix was relatively simple once I understood the problem, but it took way too long to figure out because I had made an incorrect assumption about how LDAP authentication worked. Sometimes the most frustrating bugs are the ones where your code is working exactly as written.

Previous Post Next Post

نموذج الاتصال