The dreadful ‘User must change password on next log on’ flag on LDAP

A few weeks ago I had a requirement on the system that I'm currently working on that the users should log on using a form, but checking their credentials against an Active Directory.

"No Problem", I thought. Just validate the user using LDAP and everything is fine. And it was:

bool ValidateUser(string userName, string password)
{
    try
    {
        var userOk = new DirectoryEntry("LDAP://",
                                        userName,
                                        password,
                                        AuthenticationTypes.Secure |
                                        AuthenticationTypes.ServerBind);
        return true;
    }
    catch (COMException ex)
    {
        /*
         * 0x8007052E == -2147023570
         * -- Wrong user or password
         */
        if (ex.ErrorCode == -2147023570)
            return false;
        else
            throw;
    }
}


Simple and to the point.

But... (there is always a but doesn't it?)

If the user has the flag "User must change the password on next log on" set in the Active Directory, the above code fails miserably. It doesn't validate the user. I mean, it reports "wrong user or bad password". Even if the user types the password correctly.

It took me almost 6 hours to figure it out, and I searched far and wide on the Internet, and everywhere I looked I found was people complaining about how it can't be done.

With a little nudge in the right direction from a post in CodeProject that made me change the code above to:

bool ValidateUser(string userName, string password) 
{ 
    try 
    {
        var user = new DirectoryEntry("LDAP://", 
                                      userName, 
                                      password); 
        var userObj = user.NativeObject; 
        return true; 
    } 
    catch (DirectoryServicesCOMException ex) 
    { 
        /* 
         * 0x8007052E == -2147023570
         * -- Wrong user or password
         */
        if (ex.ErrorCode == -2147023570)
            return false; 
        else 
            throw; 
     } 
}


I was finally able to figure out how to properly handle the "User must change password on next log on" flag (Which is not quite a flag, but more on that later).

It turns out that the exception thrown by the query to the NativeObject once the DirectoryEntry does not have very much useful information.

When a user with that flag on logs on with the correct password, the ErrorCode is the same and the ExtendedError is the same as if the user entered a wrong password. Almost nothing diferentiates the two.

The only difference that I was able to fathom was on the ExtendedErrorMessage. Both error messages were exactly the same, but the final part.

When a user entered a wrong password the ExtendedErrorMessage ended with the following string:

... data 52e, v1772


And when a user entered the correct password the ending was:

... data 773, v1772


Right there was my breakthrough. The way I figured out how to handle that damn flag in a way that were consistent.

That "773" part in the error message I found out that is related to the ERROR_PASSWORD_MUST_CHANGE return code from the LogonUser function from Win32API.

So I changed my validation code to:

bool ValidateUser(string userName, string password) 
{ 
    try 
    { 
        var user = new DirectoryEntry("LDAP://", 
                                      userName, 
                                      password); 
        var obj = user.NativeObject; 
        return true; 
    } 
    catch 
    (DirectoryServicesCOMException ex) 
    { 
        /* 
         * The string " 773," was discovered empirically 
         * and it is related to the 
         * 
         * ERROR_PASSWORD_MUST_CHANGE = 0x773 
         *
         * that is returned by the LogonUser API. 
         * 
         * However this error code is not in any 
         * value field of the error message, therefore 
         * we need to check for the existence of 
         * the string in the error message. 
         */ 
        if (ex.ExtendedErrorMessage.Contains(" 773,")) 
            throw new UserMustChangePasswordException(); 
        return false; 
    } 
    catch 
    { throw; } 
}


Ah, yes! The "User must change password on next log on" flag is not a flag at all. When you set this flag on the Active Directory user control pannel all it does is to set the property pwdLastSet of the user to ZERO.

Comments