I recently encountered a frustrating issue with my internal password reset tool. Users were consistently getting a generic "invalid credentials" error when trying to reset their passwords, even when we knew their current passwords were correct - we have been down this rabbit hole before with a different problem.
The tool was supposed to validate the user's current password before allowing them to set a new one, but the generic error message was masking the real underlying problems.
The Investigation
The password reset website followed this logical flow:
- User enters current password for verification
- If current password is valid, allow password reset using service account
- If current password is invalid, deny the reset
This seemed secure and straightforward, but I was seeing a high failure rate with users getting "Current credentials are invalid" errors that didn't make sense.
The Root Cause Discovery
After extensive troubleshooting (by talking to people using the password reset application) I discovered the real issue: expired passwords.
Here's what was actually happening:
- User's password had expired
- User tried to reset their password using the tool
- Tool attempted to authenticate the user with their expired password
- Active Directory correctly rejected the expired password
- Tool displayed generic "invalid credentials" error
- User was stuck in a loop - they couldn't reset because their password was expired, but they couldn't authenticate because their password was expired
The LDAP Error Code Solution
The breakthrough came when I realized that Active Directory returns different LDAP error codes for different authentication failure scenarios:
- Wrong Password:
0x8007052e
(LDAP error 49, substatus 52e) - Password Expired:
0x80070532
(LDAP error 49, substatus 532) - Must Change Password:
0x80070773
(LDAP error 49, substatus 773) - Account Disabled:
0x80070533
(LDAP error 49, substatus 533)
By catching these specific error codes, I could distinguish between "user entered wrong password" (security failure) and "user entered correct but expired password" (legitimate user needing help).
The Diagnostic Script
I created this PowerShell script to test and validate the solution:
# Test both user authentication AND service account reset
Write-Host "Password Reset Full Test" -ForegroundColor Green
Write-Host "======================" -ForegroundColor Green
# Get target username
$targetUser = Read-Host "Enter target username"
Write-Host "`nStep 1: Testing user authentication (current password)" -ForegroundColor Cyan
$currentPassword = Read-Host "Enter user's CURRENT password" -AsSecureString
# Test if current password is valid
try {
$cred = New-Object System.Management.Automation.PSCredential($targetUser, $currentPassword)
# Try to authenticate - we'll use DirectorySearcher for this
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$contextType = [System.DirectoryServices.AccountManagement.ContextType]::Domain
$context = New-Object System.DirectoryServices.AccountManagement.PrincipalContext($contextType, $env:USERDOMAIN)
# Convert secure string for authentication test
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($currentPassword)
$plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
$isValid = $context.ValidateCredentials($targetUser, $plainPassword)
if ($isValid) {
Write-Host "✓ SUCCESS: User authentication passed" -ForegroundColor Green
Write-Host "`nStep 2: Testing service account password reset" -ForegroundColor Cyan
Write-Host "Enter NEW password to set:"
$newPassword = Read-Host "New Password" -AsSecureString
try {
Set-ADAccountPassword -Identity $targetUser -NewPassword $newPassword -Reset
Write-Host "✓ SUCCESS: Service account reset worked!" -ForegroundColor Green
Write-Host "`nBoth steps passed - your tool should work for this user" -ForegroundColor Green
} catch {
Write-Host "✗ FAILED: Service account reset failed" -ForegroundColor Red
Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Red
}
} else {
Write-Host "✗ FAILED: User authentication failed" -ForegroundColor Red
Write-Host " Current password is incorrect" -ForegroundColor Red
}
$context.Dispose()
} catch {
Write-Host "✗ FAILED: Authentication test error" -ForegroundColor Red
Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Red
}
This script helped me identify which users were failing due to expired passwords versus other issues, this drove me to create a IIS website using aspx to show the status of the users passwords to confirm this, you can read about that here
That diagnostics confirmed the issues when I updated the code to report but not fix at the moment then password has expired:
Updating the website (aspx.cs)
I then updated the ASP.NET website to handle expired passwords intelligently. The key was implementing LDAP error code detection:
private enum AuthenticationResult
{
Success,
WrongPassword,
PasswordExpired,
AccountDisabled,
MustChangePassword,
OtherFailure
}
private AuthenticationResult TestPasswordWithLDAPErrorCodes(string userPrincipalName, string password)
{
try
{
using (DirectoryEntry entry = new DirectoryEntry(ldapPath, correctUsername, password))
{
object nativeObject = entry.NativeObject; // Force authentication
return AuthenticationResult.Success;
}
}
catch (DirectoryServicesCOMException comEx)
{
switch (comEx.ErrorCode)
{
case unchecked((int)0x8007052e): // Wrong password
return AuthenticationResult.WrongPassword;
case unchecked((int)0x80070532): // Password expired
return AuthenticationResult.PasswordExpired;
case unchecked((int)0x80070773): // Must change password
return AuthenticationResult.MustChangePassword;
// ... other cases
}
}
}
Security Logic
The final solution maintains security while improving user experience:
- User enters current password: System attempts LDAP authentication
- If authentication succeeds: Proceed with normal password reset flow
- If authentication fails with "wrong password" error: Deny reset (security protection)
- If authentication fails with "password expired" error: Allow direct service account reset
- If authentication fails with "must change password" error: Allow direct service account reset
This approach ensures that:
- Users must still know their current password (even if expired)
- Wrong password attempts are blocked (preventing account takeover)
- Expired password users can self-service without IT intervention
- All attempts are fully logged for security auditing
Conclusion
This experience confirmed that user-facing error messages should be helpful, not just secure. While "invalid credentials" protects against information disclosure, it also prevents legitimate users from understanding and resolving their issues. By detecting the specific underlying cause and providing appropriate guidance, we can maintain security while dramatically improving the user experience.