Authentication Using SDS

One of the easiest methods available to us is to simply create a DirectoryEntry object using our client's credentials and take some action that will cause a bind. If no error occurs, we have successfully bound to the directory and we can infer that the client's credentials are valid. However, the opposite is not quite true. That is, if an error occurs, we cannot automatically assume that the client's credentials were invalid. An error could occur because of other things, such as account lockout, being disabled, or log-on hours such that the client might not be able to authenticate successfully.

Before we get too deep into this discussion, we would really like to point out that using the DirectoryEntry object strictly for authentication is actually a nonoptimal solution. It is definitely easy, but there are a number of problems with using it. First, the DirectoryEntry object was not conceived for authentication use. In high-volume applications, we will find that using DirectoryEntry is impractical, as it cannot scale. In Chapter 3, we discussed how ADSI uses connection caching to reuse existing LDAP connections. Creating many ADSI objects with different sets of credentials works directly against ADSI's caching strategy and will cause us to run out of TCP/IP wildcard ports, resulting in seemingly random errors when trying to connect to the directory. This problem is especially insidious, as we often get great results during development, but things fall apart when we deploy to our production environment. Do not even consider using ADSI for authentication if the application requires high scalability with many concurrent users. It will not work.

LDAP Binding as Authentication

Is an LDAP bind appropriate to use for authentication? This seemingly simple question provokes no end of controversy in the community, with different camps falling heavily on one side or the other. Microsoft itself seems a bit divided on this point, with some documentation suggesting that it is not appropriate and other documentation showing you how to do it!

From a practical perspective, an LDAP bind is one of the easiest mechanisms to use to authenticate a client on Windows, especially for many higher-level languages such as VBScript. Microsoft's other APIs, such as Security Support Provider Interface (SSPI) and Kerberos, are quite a bit more difficult to program and are essentially inaccessible to some of these higher-level languages. Additionally, ADSI makes it very easy to use Simple Authentication and Security Layer (SASL) or "secure" authentication with LDAP binds, so it is not necessarily dangerous to use either.

If we always take care to secure the credentials (using SSL, SASL, etc.), there is nothing wrong, per se, with using this as an authentication mechanism. In fact, from the perspective of ADAM, an LDAP bind is the only way available to authenticate an ADAM security principal, so another method of authentication is not relevant.

The real debate surrounds whether using any given LDAP binding mechanism is appropriate for a given application architecture. We will attempt to address this issue a bit more in this chapter.

It is also not the best-performing method, as there is a cost to building the schema for an object that will only be thrown away. For small or single-user implementations, the cost is probably tolerable, but using this technique, for instance, for larger ASP.NET web sites is probably a nonstarter.

Keeping in mind that the username can come in different formats, we should make sure that we have accounted for these. For a non-SSL Active Directory installation, the credentials can be passed securely using AuthenticationTypes.Secure. However, we are restricted to using the UPN format, or NT Login format, as the username. For ADAM, we can use AuthenticationTypes.None or SecureSocketsLayer (if SSL is configured). The username should be in DN format or UPN format for ADAM security principals. The UPN format for ADAM actually can vary widely, from the more common email format (user1@adaminstance.com) to simply a username or UID format (user1).

Warning: Always Add Additional Security to Simple Binds

The username and password will be sent on the network unencrypted and vulnerable when using AuthenticationTypes.None. Always protect the credentials using SecureSocketsLayer in ADAM. If you have read this book cover to cover, you have probably already been warned about this a dozen times already. We apologize for beating you over the head, but simple binds are just not safe enough to let this go unmentioned.

 

Active Directory Authentication

Listing 12.1 demonstrates how we can use the DirectoryEntry object with Active Directory to authenticate credentials. The implementation shown is a fairly naïve implementation using RootDSE, but it is adequate for a variety of situations.

Listing 12.1. A Naïve Active Directory Authentication Method

const int ERROR_LOGON_FAILURE = -2147023570;

private bool AuthenticateUser(
 string username,
 string password,

 string domain)
{
 //optionally add the domain
 string adsPath = String.Format(
 "LDAP://{0}rootDSE",
 (domain != null && domain.Length > 0) ? domain + "/" :
 String.Empty
 );

 DirectoryEntry root = new DirectoryEntry(
 adsPath,
 username,
 password,
 AuthenticationTypes.Secure
 | AuthenticationTypes.FastBind
 );

 using (root)
 {
 try
 {
 //force the bind
 object tmp = root.NativeObject;
 return true;
 }
 catch (System.Runtime.InteropServices.COMException ex)
 {
 //some other error happened, so rethrow it
 if (ex.ErrorCode != ERROR_LOGON_FAILURE)
 throw;
 return false;
 }
 }
}

We recommend RootDSE as the object to use in our ADsPath to test the bind. The reason for this is that the object is already known and is available anonymously, so no authorization demands are placed on reading it. We can be fairly certain that the underlying bind will simply test the user's credentials.

We mentioned earlier that using something like Listing 12.1 in a large ASP.NET site to authenticate credentials would probably not be a good idea. It is relatively slow for very high-volume applications and it tends to scale poorly. It is most appropriate for fat-client types of applications where we don't have to worry as much about other users or scaling requirements.

ADAM Authentication

We can just slightly modify our previous example in order to authenticate users in ADAM or bind proxies to Active Directory users using an LDAP simple bind. We will need to use the name of the server in conjunction with RootDSE in order to find our ADAM instance. Additionally, the domain parameter for our AuthenticateUser method does not make sense for ADAM. If we simply rename "domain" to "server" and fix up the references a bit, we are almost there. The last minor change will be to use AuthenticationTypes.SecureSocketsLayer in order to protect any credentials and to switch to a simple bind, as discussed in Chapter 8. With only a few minor changes, we have a working ADAM authentication mechanism using System.DirectoryServices (SDS), as Listing 12.2 demonstrates.

Listing 12.2. ADAM Authentication Using SDS

const int ERROR_LOGON_FAILURE = -2147023570;

private bool AuthenticateUser(
 string username,
 string password,
 string server)
{
 //optionally add the domain
 string adsPath = String.Format(
 "LDAP://{0}/rootDSE",
 server
 );

 DirectoryEntry root = new DirectoryEntry(
 adsPath,
 username,
 password,
 AuthenticationTypes.SecureSocketsLayer
 | AuthenticationTypes.FastBind
 );

 using (root)
 {
 try
 {
 //force the bind
 object tmp = root.NativeObject;
 return true;
 }
 catch (System.Runtime.InteropServices.COMException ex)
 {
 //some other error happened, so rethrow it
 if (ex.ErrorCode != ERROR_LOGON_FAILURE)
 throw;

 return false;
 }
 }
}

The user's distinguished name (DN) is typically the name that we use with ADAM for authentication. However, our clients will most likely not remember or even know their DN. To cope with this, we can assign users a log-on name and store it in ADAM, using the userPrincipalName attribute. Once this attribute has been assigned, we can then use it as the username for our ADAM users. It can be passed instead of the DN for authentication in the DirectoryEntry object.

What if we want to use ADAM's pass-through authentication mechanism to authenticate Windows users? We discussed pass-through authentication in Chapter 8 and mentioned that to use it, we simply need to use a secure bind rather than a simple bind. As it turns out, Listing 12.1 will basically work for this if we specify the ADAM instance name correctly in our ADsPath, so we are already covered.

If we need to support both Windows and ADAM users, then we need to handle both secure and simple binds in our code. This can be tricky, as we essentially need a way to differentiate our users based on the username they supply so that we know which approach to take, or that we need to try both. Given that naming conventions will tend to vary from implementation to implementation, we do not have a specific sample to recommend beyond a combination of Listings 12.1 and 12.2. However, we do recommend choosing a naming convention for users in Active Directory and ADAM that makes this easy. A little planning will go a long way here.

Authentication Using SDS P

Part I: Fundamentals

Introduction to LDAP and Active Directory

Introduction to .NET Directory Services Programming

Binding and CRUD Operations with DirectoryEntry

Searching with the DirectorySearcher

Advanced LDAP Searches

Reading and Writing LDAP Attributes

Active Directory and ADAM Schema

Security in Directory Services Programming

Introduction to the ActiveDirectory Namespace

Part II: Practical Applications

User Management

Group Management

Authentication

Part III: Appendixes

Appendix A. Three Approaches to COM Interop with ADSI

Appendix B. LDAP Tools for Programmers

Appendix C. Troubleshooting and Help

Index



The. NET Developer's Guide to Directory Services Programming
The .NET Developers Guide to Directory Services Programming
ISBN: 0321350170
EAN: 2147483647
Year: 2004
Pages: 165

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net