c# - Validating expired domain credentials across the forest - c#

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?

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.

Related

How to validate an expired Active Directory password using .NET or LDAP

The basic question
Is there a way validate an Active Directory password if it is expired, using .NET or some other kind of LDAP query? To be clear, I'm not talking about checking whether the password is expired, I'm talking about verifying that an expired password is correct.
I found this answer that explains how it can be done using the Win32 API's LogonUser() function. I've asked a separate question regarding problems with getting this to work from PowerShell. Here, I'm asking if it can be done by any means other than the Win32 API, preferably .NET.
Further consideration of the topic
The PrincipalContext.ValidateCredentials method and the DirectoryEntry constructor both return the same logon failure message whether the the password is incorrect, expired, or needs to be changed at the next logon. There's no way to distinguish between an incorrect password and a correct but expired password.
Active Directory does not expose the password hashes, and AFAIK there is no way to work around that (which is probably a good thing...), so trying to make the script hash the password and compare it to the directory is a dead end.
The best solution I've been able to come up with using .NET (or anything other than the Win32 API, for that matter), is this kludgy workaround:
Check whether the password is expired (by checking whether the difference between the pwdLastSet date and the current date is greater than the maximum password age).
If it is expired, check whether it is locked out. If it is, unlock it.
Read the badPwdCount attribute.
Attempt to authenticate by any means that increases badPwdCount if it fails (ValidateCredentials, DirectoryEntry, the runas command, many other ways). Verify that a logon error is thrown.
Read badPwdCount again. If it has increased, the password provided is incorrect. If it has not increased, the password is incorrect.
(Step 2 is necessary because failed authentication attempts do not increase badPwdCount if the account is locked out.)
However, I see a few weaknesses to this plan:
It can produce false results if an authentication attempt from another source occurs between the "before" and "after" readings of badLogonCount, or if badLogonCount is reset by an administrator or the expiration of the lockout duration.
The script must ensure that the same domain controller is used throughout, to avoid false results due to convergence delays.
In some cases, the script must make changes (unlock the account), otherwise it won't work. Ideally, a validation operation shouldn't change any aspect of the data it examines.
Although these issues would be encountered rarely, and I can think of ways to mitigate them, those ways are pretty messy and not 100% reliable, and overall this is far from an ideal solution.
Is there any direct way to check this with .NET or any kind of LDAP query—some means of authenticating that produces error results that distinguish between invalid credentials and valid credentials for an expired password, as LogonUser() does, or some other creative way of distinguishing that's not subject to pitfalls like the idea outlined above?
Similar questions, but no answers
This topic has been raised multiple times before, but there are no answers to this specific question.
How to check AD user credentials when the user password is expired or "user must change password at next logon"
Both of the answers to this one are specific to validating the credentials when the "User must change password at next logon" flag is set. On a cursory glance, the second answer appears to address the case when the password is expired, because one of the code branches throws a "1907 Password Expired" exception, but if you read the code carefully you can see that this error message is misleading; it's only thrown in cases where the "User must change password at next logon" flag is set and the credentials are correct.
Validate Expired Password in active directory
Checking validity of the user password when the password is expired in Active direcory
Both of these are answered with links to answers that suggest using LogonUser().
[Note: I added the c# and vb.net tags because although the question is language-independent, I believe these are the tags that are most likely to bring it to the attention of people knowledgeable in this subject; the other tags appear to have very low visibility on their own, and there were only 7 views in the first 5 hours.]

Custom Multi-factor Active Directory Authentication

I'll start off by saying that I have no idea if what I want can actually be done. If that's the case, do not hesitate to tell me that I'm dreaming.
I want to create a custom active directory "authenticator" in C#. By that, I mean, I would like that whenever someone logs in, their password stored in the AD is checked first, and then a second step of authentication is performed. Only if both steps pass does the user get to log in.
Now, I imagine the above isn't too far fetched, providing I wanted to integrate this authenticator into a custom product, right?. Am I totally insane for also wondering if this authenticator can be used when, say, logging into Windows itself? Or perhaps a pre-existing product which authenticates against the AD?
If I'm not dreaming, would anyone also know of any good articles/APIs to get me going? The APIs don't have to be free, as I'm willing to part with some cash to get things moving faster.
This is entirely feasible. However I'd like to note that, when issuing a server bind to Active Directory, you're checking the provided username (usually the sAMAccountName) and the password entered in one action. There are a few ways of doing this in C#, but many folks (including myself) have opted to use the System.DirectoryServices and System.DirectoryServices.Protocols namespace.
This is how I currently bind users to Active Directory, which then based on the result of this method, I either display the reason for authorization failure, or I allow them to continue on to their account within the application.
//Define your connection
LdapConnection ldapConnection = new LdapConnection("123.456.789.10:389");
try
{
//Authenticate the username and password
using (ldapConnection)
{
//Pass in the network creds, and the domain.
var networkCredential = new NetworkCredential(Username, Password, Domain);
//Since we're using unsecured port 389, set to false. If using port 636 over SSL, set this to true.
ldapConnection.SessionOptions.SecureSocketLayer = false;
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; };
//To force NTLM\Kerberos use AuthType.Negotiate, for non-TLS and unsecured, use AuthType.Basic
ldapConnection.AuthType = AuthType.Basic;
ldapConnection.Bind(networkCredential);
}
catch (LdapException ldapException)
{
//Authentication failed, exception will dictate why
}
}
If you'd like to go a step further and retrieve properties about this user as well, check out this thread here.
Also, I highly recommend Softerra's LDAP Browser for testing anything LDAP related - it is a wonderful product, and it's free. You can download it from here.
Hopefully that gets you going in the right direction.

active directory authentication with status/error code result

Currently we (myself and my company) have an asp.net mvc4 page. We wish to utilize a logon page which authenticates via AD. One requirement being with an unsuccessful attempt we give back some information to the user.
The information we would like to have would be something like:
Invalid user/pw
Account is locked
Password expired
This is unfamiliar territory so I'm not sure what .NET libraries may be available. So far I've only come across the System.DirectoryServices but it doesn't seem I will get results beyond a bool.
Is this possible? Any references, suggestions, or examples would be greatly appreciated!
You can use PrincipalContext.ValidateCredentials to validate your credentials first. If false is returned, use the static UserPrincipal.FindByIdentity to find your user then, if found, look to see if the account is locked out using IsAccountLockedOut().
You might need to extend UserPrincipal yourself to see if the password is expired, I'm not seeing a direct property/method. You can extend it to access the userAccountControl attribute directly and check to see if bit 0x800000 is set, which is PasswordExpired. Here is more information on the userAccountControl values.
Suppose you have code like this
try
{
SearchResult result = searcher.FindOne();
}
catch(Exception e)
{
// now what?
}
Now in Exception you can deal with LDAP exception type, Here is the List of all LDAP error's.
http://msdn.microsoft.com/en-us/library/aa746530(v=vs.85).aspx
You can identify on the basis of ADSI Error Value which type of error you are getting.
But according to me you should give user a single common error like invalid credentials because LDAP error are much hard to deal with.
Cheers.!!

Forgot Password

I am trying to create a web application to reset the password based on question/answer using System.Web.Security API.
I get an exception:
DirectoryServicesCOMException (0x8007202f): A constraint violation
occurred" if user provide one bad answer to the question.
If I reset value of attributeMapFailedPasswordAnswerCount to not set the account becomes active again.
Account Lockout threshold in AD is set to 20 logon attempts.
I am novice on AD knowledge and will be grateful if someone can guide me how to solve this problem.
Thank you.
I'm guessing you're using ASP.NET? I don't really have any experience with it, nor do I have much experience with .NET in general (I'm still learning myself), but this was a really useful link providing examples of various Active Directory API's (link). Including resetting a user password. Here is a link to the DirectoryEntry class, if you aren't sure how to set it up (link). Plus, just browsing through the namespace documentation is very, very helpful (link). Probably the only thing I like about Microsoft is their good documentation.
I usually do something like this (in IronPython, so it will not translate directly to code you can use):
ou = System.DirectoryServices.DirectoryEntry("LDAP://ou=Users,dc=whatever,dc=something,dc=localetc")
search = System.DirectoryServices.DirectorySearcher(ou, "(samAccountName="+acc"+")", Array[str](["distinguishedName"]]))
result = search.FindAll() # note 1
if result.Count != 1:
raise BadError
else:
ent = System.DirectoryServices.DirectoryEntry(result[0].Properties["distinguishedName"][0])
ent.Username = admin # note 2
ent.Password = pwd
ent.Invoke("SetPassword", Array[object](["newpassword!"]))
ent.Properties["LockOutTime"].Value = 0
ent.CommitChanges()
Notes:
If this ever returns more than one result, you have issues.
this and the password are only necessary if the account running this does not have permission to change the user. I run these on an unprivelaged account so I have to include my admin credentials in the script (don't worry, they aren't hardcoded)
Oh and you're account lockout threshold is quite high. I would suggest 3-5, depending on the aptitude of your users.

How can I convince Internet Explorer to allow authentication as another user?

Thanks for reading and for your thoughts; this is a hairy problem, so I thought I'd share to see if it is actually a fair challenge for more seasoned developers than ourselves.
We're developing a web application for a corporate Microsoft Active Directory environment, and we use Windows Authentication provided by IIS to authenticate users for single-sign-on, alongside Forms Authentication. I know IIS complains when both are enabled, but it works very well, and every site we've deployed at has had no weird quirks to work around - until now.
The new site has "shared" machines, logged in permanently with a generic account that has read-only access to the applications they need to use. This means that we can't differentiate between users who should have different permissions to the application; we need some way of prompting the user for authentication details.
First try was some serious googling; nobody else in the world seemed to have our problem except for a few misguided souls who had asked questions into the ether and received no response.
After a bit of brainstorming and nutting out the way IIS's authentication works, it seemed that the most straightforward way to approach the problem was to issue a 401 Unauthorized in response to a user known to be a shared account. Initial tests here seemed fruitful, yielding successful changes of username inside the browser, however a prototype at the site did not prompt for credentials, and the browser kept the same account details. We also hit on the IE-specific javascript
document.execCommand("ClearAuthenticationCache")
which, again, worked in the lab but not onsite. Further experiments with IE security settings onsite revealed that the browser would automatically reauthenticate if the webapp site was excluded from the Intranet Zone, regardless of the method used to trick the browser into prompting the user for new account details.
Now we're stuck. We've got workaround options for getting it going on time, but they're definitely not the "right" answers:
require users to log out of the shared account before logging into our app (...yuck)
exclude our webapp from Intranet Zone on all machines
provide a non-SSO login service for users
I'm convinced that there's a canonical way to do this - a known pattern, a common base problem that's already been solved, something like that - and I'm very interested to hear what sort of inventive methods there are to solve this sort of problem, and if anyone else has actually ever experienced anything remotely like it.
We ended up settling on a solution that submits a query to the LDAP directory the server knows about. It means having to accept the user's password, but no other solution was solid enough to run in a production environment.
Hopefully this helps someone. .NET Framework 3.5+ required.
using System.DirectoryServices.AccountManagement;
private static bool IsLdapAuthenticated(string username, string password)
{
PrincipalContext context;
UserPrincipal principal;
try
{
context = new PrincipalContext(ContextType.Domain);
principal = Principal.FindByIdentity(context, IdentityType.SamAccountName, username) as UserPrincipal;
}
catch (Exception ex)
{
// handle server failure / user not found / etc
}
return context.ValidateCredentials(principal.UserPrincipalName, password);
}
Could you not create a page to which the shared accounts are denied access. Then do a redirect to that page, with a return URL encoded in the query string, at any point where you need the user to reauthenticate with a non-shared account? This should trigger the browser to put up the usual login dialog.
After the user reauthenticates, the new page should just redirect back to the return URL in the query string.

Categories