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:
- First, find the user in Active Directory using case-insensitive search
- 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 findADMJDoe1
- Authenticating as
admjdoe1
might fail if the system expectsADMJDoe1
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.