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

Password Reset Troubleshooting: Solving the Expired Password Problem


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:

  1. User enters current password for verification
  2. If current password is valid, allow password reset using service account
  3. 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:

  1. User enters current password: System attempts LDAP authentication
  2. If authentication succeeds: Proceed with normal password reset flow
  3. If authentication fails with "wrong password" error: Deny reset (security protection)
  4. If authentication fails with "password expired" error: Allow direct service account reset
  5. 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.

Previous Post Next Post

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