Monica
Monica

Reputation: 223

How to check AD user credentials when the user password is expired or "user must change password at next logon"

I would like to find out if there is any .Net way to validate an Active Directory user credential even if the user's password is expired or the user has "user must change password at next logon" set. I have tried PrincipalContext.ValidateCredential and this returns false for my user. I also tried Ldap Bind and that does not work either. My purpose is to authenticate the user and then prompt him with a change password dialog if his password is expired or he has to change passwored at next login.

Upvotes: 3

Views: 12036

Answers (4)

Saeb Amini
Saeb Amini

Reputation: 24449

Inspired by Pedro's answer, here's another simpler way to temporarily "unexpire" the user's password, so that ValidateCredentials can be used:

var user = UserPrincipal.FindByIdentity(PrincipalContext, username);
user.PasswordNeverExpires = true;
user.Save();

Now this works:

var canLogin = PrincipalContext.ValidateCredentials(username, password);

After verification, the flag can be reset:

user.PasswordNeverExpires = false;
user.Save();

Upvotes: 1

BobK
BobK

Reputation: 148

I have found absolutely NO WAY to validate a user if the "User must change password at next log on" flag is set.

This is the solution I came up with. Sorry it is in VB, it is inherited code :)


    ' Authenticate Using the Administrator Account
    Dim domainAndUserName As String = tmpDomain + "\" + tmpUser
    Dim entry As DirectoryEntry = New DirectoryEntry(sLDAP_PATH, m_AD_BIND_USERNAME, m_AD_BIND_PASSWORD)
    Dim ds As DirectorySearcher = New DirectorySearcher(entry)
    ds.Filter = "(&(objectClass=user)(anr=" + tmpUser + "))"

    ' Lookup the User
    Dim user As SearchResult = ds.FindOne()

    ' Check the User Status

    ' If the "User must change password at next logon" flag is set
    ' then we need to clear it before we can test the users credentials                
    If Convert.ToInt64(user.Properties("pwdLastSet").Item(0)) = 0 Then
        Dim deUser As DirectoryEntry = user.GetDirectoryEntry()
        deUser.Properties("pwdLastSet")(0) = -1
        deUser.CommitChanges()

        Dim isValidCreds As Boolean = True

        ' Create Directory Entry for User
        entry = New DirectoryEntry(sLDAP_PATH, domainAndUserName, Password)

        ' Attempt to create NativeObject based on credentials
        ' If it fails the credentials are invalid
        Try
            Dim obj As Object = entry.NativeObject()
        Catch ex As Exception
            isValidCreds = False
        End Try

        ' Reset the flag
        deUser.Properties("pwdLastSet")(0) = 0
        deUser.CommitChanges()

        ' If the credentials are valid, return '1907'
        ' if the credentials aren't valid, return '1326'
        If isValidCreds Then
            Throw New Exception("1907") ' Password Expired
        Else
            Throw New Exception("1326") ' Invalid Login
        End If
    ElseIf Convert.ToInt64(user.Properties("userAccountControl").Item(0)) = 514 Then
        Throw New Exception("1331") ' Account Disabled
    ElseIf user.Properties("lockoutTime").Count > 0 AndAlso Convert.ToInt64(user.Properties("lockouttime").Item(0)) > 0 Then
        Throw New Exception("1909") ' Account Locked Out
    End If

Only thing to be aware of is there is a potential for an issue due to slow replication, i.e., you may set the flag but since the update hasn't replicated to all of your DCs when you attempt to create the 'NativeObject' it may still fail.

Upvotes: 0

Pedro
Pedro

Reputation: 1344

We have several AD controllers in our setup and the PrincipalContext.ValidateCredentials method would always return false on the AD controllers on Windows 2003 servers on users with the "user must change password at next logon" checkbox checked.

But on the ones on Windows 2008 R2 servers, it would return true if the creds were valid even if the checkbox was checked.

So I just made sure my code was hitting one of the windows 2008 R2 servers and that did the trick.

I did work on a solution for the 2003 servers (before I realized things would just work on the other ones). Here is the code:

var adContext = new PrincipalContext(ContextType.Domain, adLocation, adContainer, adAdminUsername, adAdminPassword);

var initialValidation = adContext.ValidateCredentials(username, password);
Console.WriteLine("Initial validation returned: " + initialValidation);

if (!initialValidation)
{
    // maybe validation failed because "user must change password at next logon".
    // let's see if that is the case.

    var user = UserPrincipal.FindByIdentity(adContext, username);
    if (user.LastPasswordSet == null)
    {
        // the user must change his password at next logon. So this might be
        // why validation returned false

        // uncheck the "change password" checkbox and attempt validation again

        var deUser = user.GetUnderlyingObject() as DirectoryEntry;
        var property = deUser.Properties["pwdLastSet"];
        property.Value = -1;
        deUser.CommitChanges();

        // property was unset, retry validation
        adContext.ValidateCredentials(username, password);
        Console.WriteLine("Secondary validation returned: " + adContext.ValidateCredentials(username, password));

        // re check the checkbox
        property.Value = 0;
        deUser.CommitChanges();
  }
}

Upvotes: 7

Strillo
Strillo

Reputation: 2982

This article might help. You can use DirectorySearcher to find out if a user exists and what the password status is.

Upvotes: 0

Related Questions