.NET Core LDAP

Home / .NET Core LDAP

.NET Core doesn’t have built-in support for LDAP (Active Directory). This can be a show-stopper for a lot of projects. It was a bit of a show-stopper for me earlier as well.

So, references to these libraries won’t be available:

System.DirectoryServices
System.DirectoryServices.AccountManagement
System.DirectoryServices.Protcols

But, there are alternatives to mitigate the problem.


Reading through the Microsoft Github issues, some of these libs may be added in .NET Core 2.0, but I have no idea what the implementation will look like.

In the meantime, here are two ports of Novell’s LDAP libraries that target .NET Core.

https://github.com/VQComms/CsharpLDAP/tree/coreclrPort
https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard

The latter is available in Nuget:

https://www.nuget.org/packages/Novell.Directory.Ldap.NETStandard/

I’ve written some LDAP queries using this library and it works well. The basic flow isn’t much different from using the DirectoryServices DirectoryEntry and search functionality. The only downside I could see is that the binding (authentication) does not take advantage of the currently logged in Windows user. A username/password must be specified. Below are my samples that will search for groups (and their children) and then find any user within those group(s).

private static ILdapConnection _conn;

void Main()
{
	var groups = SearchForGroup("MyAdGroup");
	SearchForUser("MyCompany", groups);
}

static ILdapConnection GetConnection()
{
	LdapConnection ldapConn = _conn as LdapConnection;

	if (ldapConn == null)
	{
		// Creating an LdapConnection instance 
		ldapConn = new LdapConnection() { SecureSocketLayer = true };

		//Connect function will create a socket connection to the server - Port 389 for insecure and 3269 for secure	
		ldapConn.Connect("ADServrName", 3269);

		//Bind function with null user dn and password value will perform anonymous bind to LDAP server 
		ldapConn.Bind(@"domain\username", "password");
	}

	return ldapConn;
}

HashSet<string> SearchForGroup(string groupName)
{
	var ldapConn = GetConnection();
	var groups = new HashSet<string>();

	var searchBase = string.Empty;
	var filter = $"(&(objectClass=group)(cn={groupName}))";
	var search = ldapConn.Search(searchBase, LdapConnection.SCOPE_SUB, filter, null, false);
	while (search.hasMore())
	{
		var nextEntry = search.next();
		groups.Add(nextEntry.DN);
		var childGroups = GetChildren(string.Empty, nextEntry.DN);
		foreach (var child in childGroups)
		{
			groups.Add(child);
		}
	}

	return groups;
}

static HashSet<string> GetChildren(string searchBase, string groupDn, string objectClass = "group")
{
	var ldapConn = GetConnection();
	var listNames = new HashSet<string>();

	var filter = $"(&(objectClass={objectClass})(memberOf={groupDn}))";
	var search = ldapConn.Search(searchBase, LdapConnection.SCOPE_SUB, filter, null, false);

	while (search.hasMore())
	{
		var nextEntry = search.next();
		listNames.Add(nextEntry.DN);
		var children = GetChildren(string.Empty, nextEntry.DN);
		foreach (var child in children)
		{
			listNames.Add(child);
		}
	}

	return listNames;
}

void SearchForUser(string company, HashSet<string> groups = null)
{
	var ldapConn = GetConnection();
	var users = new HashSet<string>();

	string groupFilter = (groups?.Count ?? 0) > 0 ?
		$"(|{string.Join("", groups.Select(x => $"(memberOf={x})").ToList())})" :
		string.Empty;
	var searchBase = string.Empty;
	string filter = $"(&(objectClass=user)(objectCategory=person)(company={company}){groupFilter})";
	var search = ldapConn.Search(searchBase, LdapConnection.SCOPE_SUB, filter, null, false);

	while (search.hasMore())
	{
		var nextEntry = search.next();
		nextEntry.getAttributeSet();
		users.Add(nextEntry.DN);
	}
}

Leave a Reply