active directory authentication with status/error code result - c#

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.!!

Related

c# - Validating expired domain credentials across the forest

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.

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.

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.

MVC Active Directory Membership

I am trying to make use of the active directory membership rather than SQL but there is very limited documentation available online. I have managed to connect my application to the domain controller without any problems but when you use "Context.User.Identity.Name" it comes up with DOMAIN\User. I want to basically drill down and get information such as full name, e-mail address, etc.
I just need a useful link and the searching I have done doesn't appear to have got me anywhere!
Many thanks
This should give you a bit of a clue: http://msdn.microsoft.com/en-us/library/ms973834.aspx
and here is a list of LDAP properties that you might want to play around with in the search result: http://www.computerperformance.co.uk/Logon/LDAP_attributes_active_directory.htm
Have you tried with this doc?
http://msdn.microsoft.com/en-US/library/system.web.security.activedirectorymembershipprovider%28v=vs.90%29.aspx
Can help?
If you are making use of Active Directory then you are likely using Windows Authentication. If so, all you need to do is:
Reference System.DirectoryServices.AccountManagement
In code (perhaps a controller action or model constructor)
// establishes your domain as the context for your user lookup
var principalContext = new PrincipalContext(ContextType.Domain, "domainName");
// gets the current user's UserPrincipal object
var userPrincipal.FindByIdentity(principalContext, #User.Identity.Name)
// example
var email = userPrincipal.EmailAddress;
Note:
This works because Windows Authentication means User.Identity on the current HttpContext is a WindowsIdentity and thus its Name property can be used to search AD.
You aren't limited to looking up the current user. You can use FindByIdentity() to search any value passed, and this method exists on other principals (ex. GroupPrincipal). You can also designate you wish to search by another type such as SID instead of Name.
Enjoy!

WindowsTokenRoleProvider Catastrophic Failure

I'm having a really frustrating error trying to secure an ASP.NET application using the WindowsTokenRoleProvider. For a particular user I'm seeing the following ProviderException thrown:
API failed due to error 'Catastrophic failure
As I said, this only seems to happen for a particuar user, I seem to be able to access the site fine and so have several colleagues. The only difference between us and the failing user is that they are not an administrator on the box where the site is being hosted.
From the call to GetRolesForUser. From the MSDN documentation it states that this can happen because of the following:
The currently executing user does
not have an authenticated
WindowsIdentity attached to
Page.User. For non-HTTP
scenarios, the currently executing
user does not have an authenticated
WindowsIdentity attached to
Thread.CurrentPrincipal.
username does not match the Name of
the current WindowsIdentity.
A failure occurred while retrieving
the user's Windows group
information.
I'm suspecting the issue may be related to point 3 as I've managed to use the .NET Framework debugging ability in Visual Studio 2008 to debug the code and it seems to be failing calling:
UnsafeNativeMethods.GetGroupsForUser
What I can't understand is why! And if the call is failing in a Framework library then I'm not entirely sure what I can do to resolve the issue.
Any help or suggestions on this would be grately received as I'm at a loss as to where I go from here, I'm seriously considering scrapping using the role provider in place of some other less elegant method.
Okay, after some very helpful input from a Microsoft ADC I've managed to resolve the issue.
The call to UnsafeNativeMethods.GetGroupsForUser should return a list of all AD groups which a particular user has access to (this is recursive, so will also include parent groups etc). It appears that sometimes when migrating AD profiles between domains a user can end up with an erroneous SID pointing to a group with no name associated with it. Because the call tries to grab all the SID names for a particular profile it will fail when it gets to the null entry resulting in the (rather unhelpful) "catastrophic failure" error above.
Just as a reference, the group was showing up in a dump from the whoami command line tool as follows (other groups, SIDs and domain name masked):
DOMAIN\ Deleted account S-1-2-34-123456789-123456789-123456789-12345 Mandatory group, Enabled by default, Enabled group
As you can see, the domain name is present, but the group is not. The resolution was to get a domain administrator to remove the entry from the user's profile.
I truly hope this one helps others to resolve this issue because it had me completely flummoxed!

Categories