I'm trying to authenticate using Active Directory. This works fine but how can I authenticate without placing the incorrect username/password message inside the catch block? Isn't this bad practice? Many examples I find have this suggested. Does it even matter?
public void ADLogin()
{
try
{
DirectoryEntry root = new DirectoryEntry("LDAP://" + "domain", txtUsername.Value, txtPassword.Value);
DirectorySearcher searcher = new DirectorySearcher(root, "(sAMAccountName=" + txtUsername.Value + ")");
SearchResult result = searcher.FindOne();
if (result.Properties["sAMAccountName"] != null)
{
//AD Login Success
FormsAuthentication.RedirectFromLoginPage(txtUsername.Value, Login1.RememberMeSet);
}
}
catch (Exception)
{
//AD Login failed
//Display Error Message
}
}
I've tried placing the catch block inside this if statement but it throws an exception before it reaches it:
public void ADLogin()
{
DirectoryEntry root = new DirectoryEntry("LDAP://" + "domain", txtUsername.Value, txtPassword.Value);
DirectorySearcher searcher = new DirectorySearcher(root, "(sAMAccountName=" + txtUsername.Value + ")");
SearchResult result = searcher.FindOne();
if (result.Properties["sAMAccountName"] != null)
{
//AD Login Success
FormsAuthentication.RedirectFromLoginPage(txtUsername.Value, Login1.RememberMeSet);
}
if (result.Properties["sAMAccountName"] == null)
{
//AD Login failed
//Display Error Message
}
}
There's nothing wrong with catching the exception. You don't control the code inside DirectorySearcher, so you can't help that it throws an exception if something is wrong. However, you might want to differentiate the type of exceptions thrown, so you can tell the difference between bad credentials and a network error, for example.
Note that, if the credentials are bad, the exception will be thrown by searcher.FindOne(), since you are using the user's credentials to connect to AD.
This isn't the fastest way to validate the credentials. If you want something with better performance, you could use the LdapConnection solution here. It just performs an LDAP bind with the user's credentials, without doing a search like your code does. It has the added benefit of being able to tell you why the authentication failed, as that answer describes.
If you do need to find information from the user's account, then yes, you'll need to search for it. But you should use the DirectorySearcher.PropertiesToLoad collection. If you don't specify otherwise, DirectorySearcher will retrieve every attribute that has a value for each result. That can be a lot of data you don't need (especially if your organization stores user photos in AD). Instead, you can tell DirectorySearcher which attributes you need, and it will only retrieve those:
DirectorySearcher searcher = new DirectorySearcher(root, "(sAMAccountName=" + txtUsername.Value + ")");
searcher.PropertiesToLoad.Add("sAMAccountName");
result = searcher.FindOne();
I wrote a whole article about performance considerations when programming against AD that you might find interesting: Active Directory: Better performance
What if you declared the SearchResult outside of the try/catch block?
public void ADLogin()
{
SearchResult result = null;
try
{
DirectoryEntry root = new DirectoryEntry("LDAP://" + "domain", txtUsername.Value, txtPassword.Value);
DirectorySearcher searcher = new DirectorySearcher(root, "(sAMAccountName=" + txtUsername.Value + ")");
result = searcher.FindOne();
}
catch (Exception)
{
}
if (result != null && result.Properties["sAMAccountName"] != null)
{
//AD Login Success
FormsAuthentication.RedirectFromLoginPage(txtUsername.Value, Login1.RememberMeSet);
}
else
{
//AD Login failed
//Display Error Message
}
}
Not sure why you think it's bad practice the way you had it though. The best practice (without having more context of where this code is) would probably be to do your error logging in the catch block and then re-throw; silent failure is a bad practice.
I am trying to reproduce some of the functionality of the app 'Active Directory Users and Computers", and am not finding an easy way to retrieve the A/D information for a given user, using the account name, or "samaccountname".
Currently, I create a DirectoryEntry using the domain user and password, then use the entry to instantiate a DirectorySearcher to perform a FindAll(). I then run through the resulting SearchResultCollection's SearchResults, resolving UserPrincipals and their various properties to assemble all the A/D users in the OU. This takes way too long.
DirectoryEntry entry = new DirectoryEntry(
LDAPString, domainuserid, password, AuthenticationTypes.Secure);
DirectorySearcher srch = new DirectorySearcher(entry);
SearchResultCollection results = srch.FindAll();
foreach (SearchResult sr in results)
{
DirectoryEntry de = sr.GetDirectoryEntry();
if (de.Name.IndexOf("CN=") > -1)
{
foreach (string propKey in sr.Properties.PropertyNames)
{
user = new ADUser();
UserPrincipal up = GetServiceUser(sr.Properties["samaccountname"][0].ToString());
user.AccountName = up.SamAccountName;
user.Name = up.Name;
foreach (string propKey in sr.Properties.PropertyNames)
{
switch (propKey.Trim().ToLower())
{
case "givenname":
user.SurName = sr.Properties[propKey][0].ToString();
break;
ETC...
}
}
}
}
}
I've done timings and this part of the process seems to take way more time than it should. It occurs to me that I might retrieve all the SamAccountNames of A/D users, display them in a list, and only when selected to retrieve all the relevant data for that user. I've not run into a way to do this retrieval one at a time, as-needed, and it seems that would be more efficient. Is there a programmatic way to get an A/D user's information solely by use of the SamAccountName?
Edited to Add:
I thought I had found an answer to this, but then found it to be not quite what I was looking for.
Once I wrote a little AD tool, I used FindOne to retrieve and modify detail data via the SamAccountName. I don't know exactly how performant it was compared to your solution (it really was some time ago), but it worked pretty well by then (also with plenty of items):
// get the user entry
var s = new DirectorySearcher(entry);
s.Filter = "(samaccountname=" + username + ")";
SearchResult user = s.FindOne();
// read / do some changes
var d = user.GetDirectoryEntry();
d.Properties[...]
d.Invoke(...);
d.CommitChanges();
You'll want to make use of the srch.Filter to refine your LDAP query:
DirectorySearcher srch = new DirectorySearcher(entry); // You already have this line
srch.Filter = string.Format("(&(objectCategory=person)(objectClass=user)(sAMAccountName={0}))", samAccountName);
Now you can actually just use srch.FindOne() instead of srch.FindAll() since samaccountname is unique.
I'm trying to find a user in the current domain. The code is this:
DirectoryEntry domain = new DirectoryEntry("LDAP://CN-Users, DC=" + Environment.UserDomainName);
DirectoryEntries entries = domain.Children;
try
{
// The following line causes the exception
DirectoryEntry user = entries.Find("(&(objectCategory=user)(cn=" + userName + "))", ActiveDirectoryEntryType.User.TypeName);
user.DeleteTree();
user.CommitChanges();
}
catch
{}
I'm getting an error:
An invalid dn syntax has been specified.
I also tried the following code and got the same error:
DirectoryEntry user = entries.Find(userName, ActiveDirectoryEntryType.User.TypeName);
I could not find information about the proper syntax in the help files. Does anyone know how this is done?
You have an error in this statemet:
DirectoryEntry domain = new DirectoryEntry("LDAP://CN-Users, DC=" + Environment.UserDomainName);
I almost sure that it should be: LDAP://CN=Users, instaed of LDAP://CN-Users,
Second thing is DC=" + Environment.UserDomainName which maybe wrong, because ususally it is something like this: LDAP://OU=Finance,dc=fabrikam,dc=com (there is more than one DC)
You can find all DC using powershell. Run following command:
New-Object DirectoryServices.DirectoryEntry
I am trying to access directory in XXX domain from my console application.
DirectoryEntry oDE = new DirectoryEntry("LDAP://DC=XXXX,DC=myDomain,DC=com");
using (DirectorySearcher ds = new DirectorySearcher(oDE))
{
ds.PropertiesToLoad.Add("name");
ds.PropertiesToLoad.Add("userPrincipalName");
ds.Filter = "(&(objectClass=user))";
SearchResultCollection results = ds.FindAll();
foreach (SearchResult result in results)
{
Console.WriteLine("{0} - {1}",
result.Properties["name"][0].ToString(),
result.Properties["userPrincipalName"][0].ToString());
}
}
when the line SearchResultCollection results = ds.FindAll(); executes I receive the error "There is no such object on the server."
what i am doing wrong?
Ok, so short resume of our 'chat' in the comments:
Your current problem is caused because you don't format the LDAP uri correctly.
LDAP URI Buildup = "LDAP://DC="
followed by your server uri (e.g. Test1.Test2.gov.lk) where you replace the '.' with ',DC='
So, Test1.Test2.gov.lk becomes 'LDAP://DC=Test1,DC=Test2,DC=gov,DC=lk'
I can't help you with your followup problem; I suggest creating a new question for that.
Good Luck,
Nick.
I'm trying to run a simple LDAP query using directory services in .Net.
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://someserver.contoso.com/DC=contoso,DC=com");
directoryEntry.AuthenticationType = AuthenticationTypes.Secure;
DirectorySearcher directorySearcher = new DirectorySearcher(directoryEntry);
directorySearcher.Filter = string.Format("(&(objectClass=user)(objectCategory=user) (sAMAccountName={0}))", username);
var result = directorySearcher.FindOne();
var resultDirectoryEntry = result.GetDirectoryEntry();
return resultDirectoryEntry.Properties["msRTCSIP-PrimaryUserAddress"].Value.ToString();
And I'm getting the following exception:
System.Runtime.InteropServices.COMException (0x80005000): Unknown error (0x80005000)
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne)
at System.DirectoryServices.DirectorySearcher.FindOne()
As a snippet in a Console app, this works. But when I run it as part of a WCF service (run under the same credentials), it throws the above exception.
Any suggestions?
Thanks
I had the same again and again and nothing seemed to help.
Changing the path from ldap:// to LDAP:// did the trick.
It's a permission problem.
When you run the console app, that app runs with your credentials, e.g. as "you".
The WCF service runs where? In IIS? Most likely, it runs under a separate account, which is not permissioned to query Active Directory.
You can either try to get the WCF impersonation thingie working, so that your own credentials get passed on, or you can specify a username/password on creating your DirectoryEntry:
DirectoryEntry directoryEntry =
new DirectoryEntry("LDAP://someserver.contoso.com/DC=contoso,DC=com",
userName, password);
OK, so it might not be the credentials after all (that's usually the case in over 80% of the cases I see).
What about changing your code a little bit?
DirectorySearcher directorySearcher = new DirectorySearcher(directoryEntry);
directorySearcher.Filter = string.Format("(&(objectClass=user)(objectCategory=user) (sAMAccountName={0}))", username);
directorySearcher.PropertiesToLoad.Add("msRTCSIP-PrimaryUserAddress");
var result = directorySearcher.FindOne();
if(result != null)
{
if(result.Properties["msRTCSIP-PrimaryUserAddress"] != null)
{
var resultValue = result.Properties["msRTCSIP-PrimaryUserAddress"][0];
}
}
My idea is: why not tell the DirectorySearcher right off the bat what attribute you're interested in? Then you don't need to do another extra step to get the full DirectoryEntry from the search result (should be faster), and since you told the directory searcher to find that property, it's certainly going to be loaded in the search result - so unless it's null (no value set), then you should be able to retrieve it easily.
Marc
In the context of Ektron, this issue is resolved by installing the "IIS6 Metabase compatibility" feature in Windows:
Check 'Windows features' or 'Role Services' for IIS6 Metabase
compatibility, add if missing:
Ref: https://portal.ektron.com/KB/1088/
On IIS hosted sites, try recycling the app pool. It fixed my issue.
Thanks
I had the same error - in my case it was extra slash in path argument that made the difference.
BAD:
DirectoryEntry directoryEntry =
new DirectoryEntry("LDAP://someserver.contoso.com/DC=contoso,DC=com/",
userName, password);
GOOD:
DirectoryEntry directoryEntry =
new DirectoryEntry("LDAP://someserver.contoso.com/DC=contoso,DC=com",
userName, password);
I had this error as well and for me it was an OU with a forward slash in the name: "File/Folder Access Groups".
This forum thread pointed me in the right direction. In the end, calling .Replace("/","\\/") on each path value before use solved the problem for me.
Just FYI, I had the same error and was using the correct credentials but my LDAP url was wrong :(
I got the exact same error message and code
Just had that problem in a production system in the company where I live... A webpage that made a LDAP bind stopped working after an IP changed.
The solution...
... I installed Basic Authentication to perform the troubleshooting indicated here: https://support.microsoft.com/en-us/kb/329986
And after that, things just started to work. Even after I re-disabled Basic Authentication in the page I was testing, all other pages started working again with Windows Authentication.
Regards,
Acácio
I encounter this error when I'm querying an entry of another domain of the forrest and this entry have some custom attribut of the other domain.
To solve this error, I only need to specify the server in the url LDAP :
Path with error = LDAP://CN=MyObj,DC=DOMAIN,DC=COM
Path without error : LDAP://domain.com:389/CN=MyObj,DC=Domain,DC=COM
This Error can occur if the physical machine has run out of memory.
In my case i was hosting a site on IIS trying to access the AD, but the server had run out of memory.
I had to change my code from this:
DirectoryEntry entry = new DirectoryEntry(path, ldapUser, ldapPassword);
DirectorySearcher searcher = new DirectorySearcher();
searcher.SearchRoot = entry;
searcher.SearchScope = SearchScope.Subtree;
To this:
DirectoryEntry entry = new DirectoryEntry(path, ldapUser, ldapPassword);
DirectorySearcher searcher = new DirectorySearcher();
searcher.SearchScope = SearchScope.OneLevel;
SearchResult searchResult = searcher.FindOne();
The same error occurs if in DirectoryEntry.Patch is nothing after the symbols "LDAP//:". It is necessary to check the directoryEntry.Path before directorySearcher.FindOne(). Unless explicitly specified domain, and do not need to "LDAP://".
private void GetUser(string userName, string domainName)
{
DirectoryEntry dirEntry = new DirectoryEntry();
if (domainName.Length > 0)
{
dirEntry.Path = "LDAP://" + domainName;
}
DirectorySearcher dirSearcher = new DirectorySearcher(dirEntry);
dirSearcher.SearchScope = SearchScope.Subtree;
dirSearcher.Filter = string.Format("(&(objectClass=user)(|(cn={0})(sn={0}*)(givenName={0})(sAMAccountName={0}*)))", userName);
var searchResults = dirSearcher.FindAll();
//var searchResults = dirSearcher.FindOne();
if (searchResults.Count == 0)
{
MessageBox.Show("User not found");
}
else
{
foreach (SearchResult sr in searchResults)
{
var de = sr.GetDirectoryEntry();
string user = de.Properties["SAMAccountName"][0].ToString();
MessageBox.Show(user);
}
}
}
Spent a day on my similar issue, but all these answers didn't help.
Turned out in my case, I didn't enable Windows Authentication in IIS setting...
In my case, the problem was that I was trying to reference a DirectoryEntry's property value, even though that DirectoryEntry did not have that property at all.
If you for example, have:
var myGroup = new DirectoryEntry("LDAP://CN=mygroup,OU=mydomain....", myUsername, myPassword);
var groupManager = myGroup.Properties["managedBy"].Value.ToString();
If myGroup has no managedBy attribute set in the AD, this will result in Unknown error (0x80005000)