Connecting to LDAP Using .NET and C#
In a previous project, I was given a task to connect to an LDAP server using .NET dan C# to do some user related operations, mainly for authentication. .NET has a pretty good support for LDAP, especially for Ms Active Directory, the Microsoft's LDAP. I found that for other kind of LDAP servers, it's quite hard to find good tutorials. I was using OpenLDAP.
So, I thought of writing this article. Before we start, it's better that we get some understanding first on what LDAP is. Terms and definitions I use here won't be entirely correct, but sufficient enough I guess to get the job done.
What is LDAP
From Wikipedia: "The Lightweight Directory Access Protocol, or LDAP, is an application protocol for querying and modifying directory services running over TCP/IP."
A directory service is commonly used to store information of people in an organization. Entries are kept hierarchically and have attributes to store it's details. You can relate to it as an electronic address or phone book.
Each entry will have a DN (Distinguished Name). This is a string that uniquely identifies the entry. For example, "cn=Ikhwan Hayat, ou=People, dc=example, dc=com".
Let's disect parts of the DN. "cn" is common name, usually the full name or nickname. It is actually a part of the attributes, that we will talk about shortly. Any attributes can be set as this part of the DN. Some uses "uid" instead of "cn".
"ou" is a container. It is used to group entries. In this example, "ou=People" is used to group entries of persons or users.
The "dc=example, dc=com" is the Domain Component. It is like a domain name. In fact, most of the time, it follows the domain name of the server the LDAP is situated at.
Entries have attributes. They are just simple key-value pairs. Value can be multiple. For example, an entry can have attributes like "phone=01234567", "uid=user1", "sn=Hayat". Keys and values must be strings of characters.
Some notable attributes are such as "uid" for user id or login name and "sn" for surname". There's a special attribute called "userPassword" for keeping passwords which are usually hashed and encoded in Base64 (only strings, remember?).
Another special attribute is "objectClass". This attribute it to indicate what kind of entry is this. An objectClass can determine what attributes are mandatory or optional for an entry. A "person" objectClass maybe have "cn" and "sn" as required attributes. A "posixAccount" have "cn", "sn", "uid", "home", and "shell" as required.
Connecting to the Server (Binding)
Enough talk, let's see some code. We will be using classes from the namespace "System.DirectoryServices.Protocols" to our LDAP operations.
Actually, the popular option is "System.DirectoryServices". This is mainly used to connect to Active Directory, it offers some abstractions to make things easier. But I personally find "System.DirectoryServices.Protocols" to be much more intuitive, so I use it instead. Also, abstractions usually includes speed overheads.
There's a another option, to use Novell's LDAP Library. I tried it, but I don't like how the code is structured. I don't feel like a natural C# code.
Using "Systems.DirectoryServices.Protocols", we use LdapConnection to connect to the server. First, you create the connection, supplying the host address and optionally a port. Second, we bind to it using a DN and a password.
// With host address only
var host = "example.com";
var conn = new LdapConnection(host);
// or with port. Host address can be an IP number
var conn = new LdapConnection(new LdapDirectoryIdentifier("172.16.100.333:389"));
// Set the DN as username and include password
var adminDn = "cn=admin, dc=example, dc=com";
var adminPassword = "p";
conn.Credential = new NetworkCredential(adminDn, adminPassword);
conn.AuthType = AuthType.Basic;
conn.SessionOptions.ProtocolVersion = 3;
conn.Bind();
An important note, for my version of OpenLDAP, I need to specifically set the LDAP protocol version as 3. I nearly cracked my head figuring this up.
Connecting is very straight forward. Next, will go for a scenario of authenticating user, changing user's password, and creating a user. These will demonstrate available LDAP operations such as searching, modifying and creating an entries.
Authenticating User (Searching)
Authentication is done by doing a bind using the user's DN and password. In some setup, the user ID is made as part of the DN, like "cn=username, ou=People, dc=example, dc=com" or "uid=username, ou=People, dc=example, dc=com". In this case, it would be dead easy to figure out the correct DN.
But we cannot actually depend on this. As I said earlier, any attribute can be made as the first part of the DN. As such, we have to search for the correct entry and retrieve the exact DN before binding.
Usernames are kept in "uid" attribute, so we just search the entry which has it. Another thing to note is that entries are kept in containers. We must first know which container stores the entries for user. Ask the sysadmin for this, but usually it's kept in "ou=People".
string uid = "ikhwan";
string container = "ou=People, dc=example, dc=com";
var conn = Connect(); // assume this method wraps
// the code above to establish
// and return an LdapConnection
// Create a search request
var searchRequest = new SearchRequest(container, // search in this container
"(uid=" + uid + ")", // a filter
SearchScope.OneLevel); // how deep to search
// Send request and receive a response
var resp = (SearchResponse)conn.SendRequest(searchRequest);
// Found the DN. Ideally, you'll check if the resp.Entries do have values first.
var userDn = resp.Entries[0].DistinguishedName;
Search is done by using an LDAP filter. In this case we search for entries having attribute "uid=ikhwan". If the user is strictly kept in a specific "objectClass", we can use a filter like "(&(objectClass=posixAccount)(uid=ikhwan))". There's a simple explaination for LDAP search filter here.
After we get the DN, do a bind using this DN and the password. If it binds successfully, then the username and password is correct.
var conn = new LdapConnection(host);
conn.Credential = new NetworkCredential(userDn, "passw0rd");
conn.AuthType = AuthType.Basic;
conn.SessionOptions.ProtocolVersion = 3;
conn.Bind();
Changing User's Password (Modifiying an Attribute)
To change the password, we change the value of the attribute "userPassword". In the previous codes, you could see that we use SearchRequest class to do a search. Similar for modifiying an entry, we use ModifyRequest.
var conn = Connect(); // assume we have a method to
// connect using admin's DN
var userDn = FindUserDnFromUid("ikhwan"); // assume we have a method to
// to find the DN based on uid
var newpass = GenerateHashFromPlainText("newpassw0rd");
var req = new ModifyRequest(userDn, // DN for entry we want to modify
DirectoryAttributeOperation.Replace,// replace the value
"userPassword", // the attribute we want to modify
newpass); // the new value
conn.SendRequest(req);
Simple right? Except for the "GenerateHashFromPlainText" bit :)
Modifiying an attribute's value is as simple as replacing it with the new value. But for passwords, you need to hash it first. Yyou can store it in plain text actually, but passwords should always be hashed/encrypted of course.
LDAP understands several type of hashing such as MD5, SHA, SSHA, etc. Just generate a Base64 encoding of the hash, and put it in place. I'll left the algorithm to create the hash for your imagination. Get some pointers here: http://users.ameritech.net/mhwood/ldap-sec-setup.html
Creating New User (Adding New Entry)
To create a new entry in LDAP, it's as easy as -- yes, you got it -- using the AddRequest class. Let's jump straight to the code.
var userDn = "cn=Joe Somebody, ou=People, dc=example, dc=com";
var attrs = new[] {
new DirectoryAttribute("objectClass", "top", "person", "OpenLDAPperson"),
new DirectoryAttribute("cn", "Joe Somebody"),
new DirectoryAttribute("uid", "joesomebody"),
new DirectoryAttribute("sn", "Somebody"),
new DirectoryAttribute("userPassword", GenerateHashFromPlainText("newuserpassw0rd"))
};
var req = new AddRequest(userDn, attrs);
conn.SendRequest(req);
Here I used the an AddRequest constructor which accepts a string to be used as DN and an array of attribute key-value pairs. There's several other variations available.
Note that I passed multiple values for attribute "objectClass". Like what's I've said before, "objectClass" determined what this entry will be used for. "objectClass" can have inheritence, for example "OpenLDAPPerson" extends from "person", and all "objectClass"-es extends from "top". You have to pass all the inheritence in.
"objectClass" will determine it's mandatory attributes. So you have to satisfy that also. For example, a "person" will need a "cn" and a "sn".
Deleting A User (Deleting an Entry)
Well, dead simple, use DeleteRequest :) It's so simple, I'm going to skip this and leave it to you as an exercise :D
Conclusion
That's it, simple operations against an LDAP server using the using classes from the System.DirectoryServices.Protocols. Operations usually involves sending requests and receiving responses. Use this MSDN article to see all the operations in details: http://msdn.microsoft.com/en-us/library/bb332056.aspx
The hard part is usually figuring what's LDAP actually is. Once you grasp the basics, it's all smooth sailing from there. Hope this tutorial helps!