0

There are already a few questions which ask how to validate Active Directory domain questions. However, I do not believe they deal adequately with multi-domain scenarios across the forest. For starters, they both suggest that the most definitive way to perform AD authentication in C# with .NET 3.5+ should look something like this:

bool isValid = false;
using(var ctx = new PrincipalContext(ContextType.Domain, "foo.com"))
{
    // verify user exists first
    var lookedUp = UserPrincipal.FindByIdentity(ctx, "myuser");
    if (lookedUp != null)
    {
        // validate credentials
        isValid = pc.ValidateCredentials("myuser", "mypassword");
    }
}

This is all well and good when the user you want to authenticate belongs to foo.com. However, there are some subtle unexpected differences in the case of a user belonging to a child domain, whose password is also expired. (I have successfully got the PrincipalContext to find the user in the child domain two different ways: (a) setting the domain argument to "foo.com:3268"; (b) adding a container argument w/ the base DN of the child domain: new PrincipalContext(ContextType.Domain, "foo.com", "dc=child,dc=foo,dc=com"). The problem I describe below occurs in both scenarios.)

If the user belongs to foo.com, and has an expired password, ValidateCredentials returns true (at least on Windows 7 where I'm testing; I've seen others say the behavior is different in Windows 2003). However, if the user belongs to child.foo.com and the password is expired, then ValidateCredentials returns false.

It is pretty important for me to be able to distinguish between "valid password but expired" and "invalid password". If the entered password is correct but expired, then I want to be able to redirect them to a "change password" screen. However, if the password they entered was totally wrong, then it could be considered leakage of information to forward them to a change password screen.

The only way forward I see is to use the LDAP interface to AD and try to parse the status messages it sends back to figure out the underlying error code. This sounds neither fun nor wise.

Can anyone provide a reason for this behavior when dealing with subdomains within the same forest? Can anyone provide a solution to what I am trying to accomplish here?

Community
  • 1
  • 1
matthauck
  • 63
  • 1
  • 5
  • Windows domains already notify on password expiration. What are you trying to do exactly? Since this isn't working, I might try to parse out the useraccountcontrol attribute (bitmask) and determine if the password is expired (const long PASSWORD_EXPIRED = 0×800000;). The statement, however, "if the password they entered is totally wrong" has me wondering if you're trying to reinvent the wheel. If they enter wrong passwords, eventually the account will just lock out. And the only way you're going to find invalid attempts is to parse the sec log on DCs - a huge effort. – Quantum Elf Jun 22 '14 at 23:34
  • I am building a separate system that uses AD accounts for login. I have tried checking for PASSWORD_EXPIRED, and it does not appear to be present if and only if the "Must change password" checkbox is checked. I am not worry about AD locking people out naturally -- I am worried about needing to lock people out of my system exactly when they would be locked out by AD. – matthauck Jun 25 '14 at 16:49

1 Answers1

1

So the issue here it appears is that .NET ultimately tries to do what's called a fast concurrent LDAP bind to AD. That's a super lightweight mechanism and Google seems to indicate that perhaps it bypasses the expiry check. I didn't validate this, but, assuming it's true...

I think your options here are to either a) do the binding yourself (look at the LDAPConnection class and the associated flags) or b) P/Invoke LogonUser. You may need to dig in to figure out the passwory expiry status if the call fails as I'm not sure if either of those will tell you that it's expired or isn't as the reason for the failure.

Brian Desmond
  • 4,473
  • 1
  • 13
  • 11