Generic Authentication Call to Active Directory in C# - c#

I would like to have a clean C# class that authenticates from Active Directory.
It should be pretty simple, it just has to ask for credentials and check if it matches what AD is expecting.
I am responsible for a number of C# applications, and I would like all of them to use the same class.
Could someone please provide a clean code sample of such a class? It should have good error handling, be well commented, and specifically ask for credentials rather than try to read if a user is already logged in to AD for another application. (This is a security requirement because some applications are used in areas with shared computers: People with multiple roles and different permission levels may use the same computer and forget to log out between sessions)

http://support.microsoft.com/kb/316748
public bool IsAuthenticated(String domain, String username, String pwd)
{
String domainAndUsername = domain + "\\" + username;
DirectoryEntry entry = new DirectoryEntry(_path, domainAndUsername, pwd);
try
{ //Bind to the native AdsObject to force authentication.
Object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
if(null == result)
{
return false;
}
//Update the new path to the user in the directory.
_path = result.Path;
_filterAttribute = (String)result.Properties["cn"][0];
}
catch (Exception ex)
{
throw new Exception("Error authenticating user. " + ex.Message);
}
return true;
}

Admittedly I have no experience programming against AD, but the link below seems it might address your problem.
http://www.codeproject.com/KB/system/everythingInAD.aspx#35

There's some reason you can't use Windows integrated authentication, and not bother users with entering their names and passwords? That's simultaneously the most usable and secure solution when possible.

Related

DirectorySearcher.ClientTimeout - Search timed out or really 0 results?

I maintain a C# program which needs to check whether thousands of Active Directory accounts are still in existence & if they are enabled or not. Recently, I've found that my program was hanging while querying the directory for an account. Fortunately, I've just discovered the DirectorySearcher.ClientTimeout property (which I hadn't been setting, meaning that the search goes on indefinitely).
The one problem that I see with using this property is that, if the search hangs while looking up an account that happens to actually exist, the DirectorySearcher.FindOne() method will return 0 results. As you can imagine, that's a problem since at runtime, I don't know whether the search failed or if the account really wasn't found.
Does anyone know if there's another property that gets set in the object that I can use to see if the search aborted? Is there any difference between a result set from an aborted search versus one that really contains zero results?
Here's my method:
public static string UserExists(string username, Log log)
{
string accountStatus;
if (username.Split('\\').Length != 2)
return "Invalid ID,Invalid ID";
else
{
try
{
string[] parts = username.Split('\\');
domain = parts[0];
ScopeDN = "DC=" + domain + ",DC=contoso,DC=com";
DirectoryEntry de = GetDirectoryEntry("LDAP://" + ScopeDN);
DirectorySearcher ds = new DirectorySearcher();
ds.SearchRoot = de;
ds.ClientTimeout = TimeSpan.FromSeconds(30);
ds.Filter = "(&(objectClass=user) (sAMAccountName=" + username + "))";
SearchResult result = ds.FindOne();
if (result == null)
accountStatus = "Does Not Exist,Account Does Not Exist";
else
{
int UAC = (int)result.Properties["userAccountControl"][0];
bool enabled = !Convert.ToBoolean(UAC & 0x00002);
if (enabled)
accountStatus = "Exists,Account is Enabled";
else
accountStatus = "Exists,Account is Disabled";
}
return accountStatus;
}
catch (Exception e)
{
log.exception(LogLevel._ERROR, e, false);
return "Exception,Exception";
}
}
}
Try doing the following optimizations:
Dispose DirectoryEntry
Dispose DirectorySearcher
Create and cache a connection to RootDse object before querying users. In this case all AD queries will use one single cached connection to AD, which significantly increases performance.
For example
var entry = new DirectoryEntry("LDAP://contoso.com/RootDSE");
entry.RefreshCache();
// making user search and other AD work with domain contoso.com here
entry.Dispose();
AFAIK, there is no difference. One thing you could do is retry a couple times before assuming it is missing. Its a matter of confidence. One failure may not give high enough confidence. But 2 or 3 might. Does that help you?

Connect to Active Directory via LDAP

I want to connect to our local Active Directory with C#.
I've found this good documentation.
But I really don't get how to connect via LDAP.
Can somebody of you explain how to use the asked parameters?
Sample Code:
static DirectoryEntry createDirectoryEntry()
{
// create and return new LDAP connection with desired settings
DirectoryEntry ldapConnection = new DirectoryEntry("rizzo.leeds-art.ac.uk");
ldapConnection.Path = "LDAP://OU=staffusers,DC=leeds-art,DC=ac,DC=uk";
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
return ldapConnection;
}
I just have the Hostname and the IP Address of our Active Directory Server. What does DC=xxx,DC=xx and so on mean?
DC is your domain. If you want to connect to the domain example.com than your dc's are: DC=example,DC=com
You actually don't need any hostname or ip address of your domain controller (There could be plenty of them).
Just imagine that you're connecting to the domain itself. So for connecting to the domain example.com you can simply write
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://example.com");
And you're done.
You can also specify a user and a password used to connect:
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://example.com", "username", "password");
Also be sure to always write LDAP in upper case. I had some trouble and strange exceptions until I read somewhere that I should try to write it in upper case and that solved my problems.
The directoryEntry.Path Property allows you to dive deeper into your domain. So if you want to search a user in a specific OU (Organizational Unit) you can set it there.
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://example.com");
directoryEntry.Path = "LDAP://OU=Specific Users,OU=All Users,OU=Users,DC=example,DC=com";
This would match the following AD hierarchy:
com
example
Users
All Users
Specific Users
Simply write the hierarchy from deepest to highest.
Now you can do plenty of things
For example search a user by account name and get the user's surname:
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://example.com");
DirectorySearcher searcher = new DirectorySearcher(directoryEntry) {
PageSize = int.MaxValue,
Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=AnAccountName))"
};
searcher.PropertiesToLoad.Add("sn");
var result = searcher.FindOne();
if (result == null) {
return; // Or whatever you need to do in this case
}
string surname;
if (result.Properties.Contains("sn")) {
surname = result.Properties["sn"][0].ToString();
}
ldapConnection is the server adres: ldap.example.com
Ldap.Connection.Path is the path inside the ADS that you like to use insert in LDAP format.
OU=Your_OU,OU=other_ou,dc=example,dc=com
You start at the deepest OU working back to the root of the AD, then add dc=X for every domain section until you have everything including the top level domain
Now i miss a parameter to authenticate, this works the same as the path for the username
CN=username,OU=users,DC=example,DC=com
Introduction to LDAP
If your email address is 'myname#mydomain.com', try changing the createDirectoryEntry() as below.
XYZ is an optional parameter if it exists in mydomain directory
static DirectoryEntry createDirectoryEntry()
{
// create and return new LDAP connection with desired settings
DirectoryEntry ldapConnection = new DirectoryEntry("myname.mydomain.com");
ldapConnection.Path = "LDAP://OU=Users, OU=XYZ,DC=mydomain,DC=com";
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
return ldapConnection;
}
This will basically check for com -> mydomain -> XYZ -> Users -> abcd
The main function looks as below:
try
{
username = "Firstname LastName"
DirectoryEntry myLdapConnection = createDirectoryEntry();
DirectorySearcher search = new DirectorySearcher(myLdapConnection);
search.Filter = "(cn=" + username + ")";
....

to retrieve windows login-password via asp.net

public Object IsAuthenticated()
{
String domainAndUsername = strDomain + "\\" + strUser;
***DirectoryEntry entry = new DirectoryEntry(_path, domainAndUsername, strPass);***
SearchResult result;
try
{
//Bind to the native AdsObject to force authentication.
DirectorySearcher search = new DirectorySearcher(entry) { Filter = ("(SAMAccountName=" + strUser + ")") };
search.PropertiesToLoad.Add("givenName"); // First Name
search.PropertiesToLoad.Add("sn"); // Last Name
search.PropertiesToLoad.Add("cn"); // Last Name
result = search.FindOne();
if (null == result)
{
return null;
}
//Update the new path to the user in the directory.
_path = result.Path;
_filterAttribute = (String)result.Properties["cn"][0];
}
catch (Exception ex)
{
return new Exception("Error authenticating user. " + ex.Message);
}
return user;
}
In the above code segment, is there a way to retrieve the user's Windows login password so that the LDAP authentication works without asking the user his password another time?
Can the value for "strPass",that is being passed when DirectoryEntry object is being created, be retrieved by any way?
The password does not exist anywhere. It would be a big security hole if it did.
Also, BTW, get rid of the try/catch block. It's doing nothing but hiding the reason for the exception.
Use the ActiveDirectoryMemebershipProvider - You can authenticate without writing code, thus eliminating the login scenario you currently have.
You could set up Windows Authentication in your ASP.NET app.
http://msdn.microsoft.com/en-us/library/ff647405.aspx
Once you set this up, only authenticated users have access to the protected parts of your site.
This gives you access some key bits of information.
For example:
System.Web.HttpContext.Current.User.Identity.Name - gives the name (domain\username) of an authenticated user.
System.Web.HttpContext.Current.User.IsInRole("role_name_here") - will tell you if the authenticated user is in a given role.
Authentication can be tricky to do for the first time - please do not ask a user for their windows password - this is a security risk - allow IIS and the .NET framework take care of this for you. The article above may be a bit long and seem a bit complicated but it has a lot of good information in it.

Creating Active Directory user with password in C#

I'm looking for a way to create Active Directory users and set their password, preferably without giving my application/service Domain Admin privileges.
I've tried the following:
DirectoryEntry newUser = _directoryEntry.Children.Add("CN=" + fullname, USER);
newUser.Properties["samAccountName"].Value = username;
newUser.Properties["userPassword"].Value = password;
newUser.Properties["mail"].Value = email;
newUser.CommitChanges();
The user is created, but it seems the password is never set on the user.
Does anyone have an idea on how to set the user's password initially when creating the user? I know about
.Invoke("SetPassword", new object[] { password })
But that requires my code to be run with Domain Admin privileges. As I don't really see the point to grant my code Domain Admin privileges, just to set the initial password (I also allow user password resets, but those run in the context of that particular user), I am hoping someone has a clever solution that doesn't require me to do so.
Thanks in advance!
You can do this whole process much easier now with System.DirectoryServices.AccountManagement (long as you're on .Net 3.5):
See here for a full rundown
Here's a quick example of your specific case:
using(var pc = new PrincipalContext(ContextType.Domain))
{
using(var up = new UserPrincipal(pc))
{
up.SamAccountName = username;
up.EmailAddress = email;
up.SetPassword(password);
up.Enabled = true;
up.ExpirePasswordNow();
up.Save();
}
}
I'd use #Nick's code (wrapped in using statements so the context and principal are disposed properly). As for privileges, you'll need to at least have enough privileges on the OU where you are creating the user to create and manage objects. I'd create a specific user under which your program will run and give it just enough privileges to do the tasks that it needs in that specific OU and no more.
Yes can also use below code to create bulk of users
DirectoryEntry ouEntry = new DirectoryEntry("LDAP://OU=TestOU,DC=TestDomain,DC=local");
for (int i = 0; i < 10; i++)
{
try
{
DirectoryEntry childEntry = ouEntry.Children.Add("CN=TestUser" + i, "user");
childEntry.CommitChanges();
ouEntry.CommitChanges();
childEntry.Invoke("SetPassword", new object[] { "password" });
childEntry.CommitChanges();
}
catch (Exception ex)
{
}
}
The actual attribute for the password is unicodePwd, which requires a specific format that is described in the documentation. But this is how you can do it:
newUser.Properties["unicodePwd"].Value = Encoding.Unicode.GetBytes("\"NewPassword\"");
Doing it this way, you can create the user with a password in one step.

Authenticating an ADAM user against ADAM from C# - cannot bind

I have set up an ADAM instance and added some test users. From c# I can bind to ADAM using a windows account but I cannot bind using one of the ADAM users. (I can successfully bind the adam users in ldp) & I have made sure the users are enabled by setting msDS-UserAccountDisabled attribute to false.
When I bind with my windows account I can successfully search & bring back properties for ADAM users but I am still struggling to authenticate them, when I try and bind with an ADAM user account I get the error :
Error: System.Runtime.InteropServices.COMException (0x8007052E): Logon failure: unknown user name or bad password. at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
Here is the code I am using:
string userName = txtUserName.Text;
string password = txtPassword.Text;
string ADConnectionString = "LDAP://localhost:389/CN=sandbox,DC=ITOrg";
DirectoryEntry entry = new DirectoryEntry(ADConnectionString);
entry.Username = "myComputer\\Administrator";
entry.Password = "myPassword";
try
{
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "(&(objectClass=user)(CN=" + userName + "))";
SearchResultCollection result = searcher.FindAll();
if (result.Count > 0)
{
//bind with simple bind
using (DirectoryEntry de = new DirectoryEntry(result[0].Path, userName, password,AuthenticationTypes.None))
{
if (de.Guid != null) // this is the line where it dies
{
Label1.Text = "Successfully authenticated";
Label2.Text = result[0].Properties["displayName"][0].ToString();
Label3.Text = result[0].Properties["telephoneNumber"][0].ToString();
} else
{
Lable1.Text = "Unable to Authenticate";
}
}
}
else
{
Lable1.Text = "UserName :" + userName + " not found";
}
} catch(Exception ex)
{
Label1.Text = "Error searching: " + ex.ToString();
}
Thanks in advance for any help, much appreciated!
It is probably a username format problem. When authenticating an ADAM user in SDS, you must use LDAP simple bind and use a name format supported by ADAM. ADAM technically allows you to use Digest auth as well, but that is not available in SDS (only SDS.Protocols), so that doesn't apply to your code approach.
You ARE using simple bind because you have AuthenticationTypes.None set so that part is ok. The part that is likely wrong then is the username format.
ADAM accepts the user's full DN, their displayName (if set and unique) and/or the userPrincipalName (if set and unique) as a "bindable" username, so start with the full DN of the user and seee if that works. If so, you can try the other user name values as well. Note that you can put whatever you want for displayName or userPrincipalName in ADAM. There is no validation. Just make sure the values are unique.
If you really want to do some type of bind authentication thing against ADAM, you'll get better perf and scale by using the ValidateCredentials method of PrincipalContext in .NET 3.5.
This kind of stuff is documented and discussed in the forums over at http://www.directoryprogramming.net all the time and is a place I frequent much more often since it is my site. :) A friend tipped me off to this post or I would have never seen it.

Categories