The LdapAuthenticator
A plug-in using LDAP authentication, which can be used to authenticate a user against Active Directory, is the first plug-in added to the solution. It extends the XmlConfiguredPlugin
class along with implementing the IAuthenticationPlugin
interface. The new project is called AuthenticationPlugin.Plugins.LdapAuthenticator
. A project reference to the AuthenticationPlugins.Common
project is added right away to allow the project to compile.
In Listing 1-2, you'll see the IsLoginValid
method from the newly-created AuthenticationPlugin.Plugins.LdapAuthenticator.Plugin
class. The call to the base class' LoadConfiguration
method gets the NameValueCollection
, which is used later in the IsLoginValid
method to find the LDAP server and to get a domain name.
The classes needed for authenticating users with LDAP are in the System.DirectoryServices
namespace, so using System.DirectoryServices needs to be added to the Plugin class as well as a reference to the System.DirectoryServices.dll
assembly. The LDAP string in the configuration file looks like LDAP://dc=domain,dc=com
if your Active Directory domain is called domain.com.
The NativeObject
call on the DirectoryEntry
object entry is an attempt to bind to the object in the directory. Since this call forces authentication, you will get an error if the user does not exist. If the user is a valid user in the domain, the call will succeed. Unfortunately, since the call throws an exception, there is a bit of a performance hit if the user is not a valid user.
Listing 1-2.
public bool IsLoginValid( string username, string password ) { bool isValid = false; try { NameValueCollection configuration = LoadConfiguration( ConfigurationRootNodeName ); /* Get the domain name from the assembly-specific configuration * file */ string domainName = configuration[ "domain" ]; string ldapPath = configuration[ "ldapPath" ]; Debug.WriteLine( string.Format( "LDAP path is '{0}'", ldapPath ) ); /* The full username for LDAP will also have the domain * with it */ string fullUsername = string.Format( @"{0}\{1}", domainName, username ); Debug.WriteLine( string.Format( "Full user name is '{0}'", fullUsername ) ); DirectoryEntry entry = new DirectoryEntry( ldapPath, fullUsername, password ); /* This will actually force authentication, and will throw an * exception * if the user and password are unknown or invalid */ object obj = entry.NativeObject; /* Just making sure at this point */ DirectorySearcher searcher = new DirectorySearcher( entry ); searcher.Filter = string.Format( "(samAccountName={0})", username ); SearchResult result = searcher.FindOne(); isValid = ( null != result ); } catch { /* Replace this code with logging, etc. */ } return isValid; }
Database authentication
I put the second plug-in into a new project called AuthenticationPlugin.Plugins.SqlAuthenticator
. This plug-in authenticates a user against values in a database. Similar to the LdapAuthenticator
plug-in, it requires some configuration for things like the database connection string. The configuration file is shown here:
<sqlAuthenticator> <connectionString>Database=;...</connectionString> </sqlAuthenticator>
When the LoadConfiguration
method is called in the IsLoginValid
method, the configuration file loads into the NameValueCollection
returned by the LoadConfiguration
method. The value of the key is accessed later in the IsLoginValid
method:
string connectionString = configuration[ "connectionString"];
That key is used when creating a new instance of the SqlConnection
class from the System.Data.SqlClient
namespace. For the most part, this is pretty standard database connection code—the only thing that might look odd to you (if you aren't used to seeing it) is the using
statement in which the SqlConnection
object is created.
The using
statement makes sure that any class that implements the IDisposable
interface is properly closed and disposed of at the end of the block, regardless of whether an exception occurred. This is the same as building a try...finally
block and putting a conn.Close()
line inside finally to make sure that even if an exception happens the database connection is closed. The using
statement is a little more brief and cleaner than the try...finally
block.
The result value of the method, isValid
, is finally assigned to true if the string result (retrieved by the ExecuteScalar
method which returns the first column of the first row found).
Summary
The key concepts discussed in this article were using inheritance to re-use code. I also showed you how plug-ins can load their own configurations, so applications that use them don't have to worry about whether the plug-in was configured correctly. Finally, I very briefly touched on two concepts: using the classes in the System.DirectoryServices
namespace to do LDAP authentication, and using classes in the System.Data.SqlClient
namespace to connect to
a database and run a stored procedure.