Geeks With Blogs
Stephen Myers Good Times with .Net
LDAP is not Active Directory, though Active Directory is LDAP. As someone who drinks the Microsoft Kool-Aid, I found myself using LDAP for something other than Active Directory and I thought I should share what I have found. The problem domain was to connect a new MVC application to an existing Sun One LDAP Store. 
First off, authenticating an MVC application using forms mode authentication and the Membership providers is straight forward. Start with the ASP.NET MVC 2 Web Application Template that comes out of the box with Visual Studio 2010 when you create a new project and it will have the code to use forms authentication with the membership provider model built in. In the web.config, add the tag:
<authenticationmode="Forms">
<forms loginUrl="~/Account/LogOn"timeout="2880" />
</authentication>
 
This  points to the \Controllers\AccountController.cs class which already contains the code that you need to perform the authentication. 

Now for the Membership provider… If you were using Active Directory, you could just use the Active Directory Membership provider, and be done without writing a lick of C# code:

 <membershipdefaultProvider="AspNetActiveDirectoryMembershipProvider">
   <providers>
     <addname="AspNetActiveDirectoryMembershipProvider"
       type="System.Web.Security.ActiveDirectoryMembershipProvider,
       System.Web, Version=2.0.3600, Culture=neutral,
       PublicKeyToken=b03f5f7f11d50a3a" />
   </providers>
 </membership>

However, since we are looking at SunOne, we need to use the Generic LDAP Membership provider. Unfortunately, this provider is only included with SharePoint. If you are working in an environment where you have access to this, check out for another code-free solution: 
http://msdn.microsoft.com/en-us/library/ms517148.aspx

Otherwise, all that you need to do is implement a membership provider that speaks vanilla LDAP so it can connect to the SunOne store and there you go. Please keep in mind the scope of this blog is only to authenticate to the SunOne store, not to be able to manage it, though carrying this implementation through to that level if needed should be pretty straight forward. The general strategy for implementing the authentication check is a little bit different than I would have expected. I would have expected to authenticate to the LDAP provider using some sort of administrative credential and then calling a “Check Password” function of some kind. The actual way to do it is bind to the LDAP server using the credentials passed in for authentication, and if the bind is successful, then Authentication = true, else, especially in the case of exception is false. I don’t like programming to exceptions so if someone has a better approach, please reply. 
In order to do this, create a new project with an output type of class library. Create a public class which will match your configuration file and have it implement MembershipProvider. You can then right click on MemberShipProvider and select “Implement Interface”  in order to generate method stubs. Then create a helper class to do the actual work. The AspNetSunOneMemebershipProvider class listing below has been truncated to exclude the methods which I did not implement. In order to compile this you will need to implement them.

 

File: AspNetSunOneMembershipProvider.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Security;
using System.DirectoryServices;
 
 
namespace SunOne
{
    public class AspNetSunOneMembershipProvider : MembershipProvider
    {
        // Password to SunOneMembershipProviderKey.pfx is SunOne
 
        private static SunOneHelper _helper;
        private string _applicationName;
 
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            // Overriding the Initialize method allows us access to the NameValueCollection that
            //  is read from the XML attributes
            // in the web.config file.
            string ldapServer = config["LDAPServer"];
            string userNameConnectString = config["UserNameConnectString"];
 
            // Scott Gu says always set the app name
            // http://weblogs.asp.net/scottgu/archive/2006/04/22/Always-set-the-_2200_applicationName_2200_-property-when-configuring-ASP.NET-2.0-Membership-and-other-Providers.aspx
            ApplicationName = config["ApplicationName"];
 
            _helper = new SunOneHelper(ldapServer, userNameConnectString);
           
            base.Initialize(name, config);
        }
 
        public override bool ValidateUser(string username, string password)
        {
            return _helper.ValidateUser(username, password);
        }
 
        public override string ApplicationName
        {
            get
            {
                return _applicationName;
            }
            set
            {
                _applicationName = value;
            }
        }
 
        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
           return GetUser(providerUserKey.ToString(), userIsOnline);
        }
 

File SunOneHelper.cs

 using System;

using System.Collections.Generic;
using System.Configuration;
using System.DirectoryServices.ActiveDirectory;
using System.DirectoryServices.Protocols;
using System.DirectoryServices;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.Text;
 
 
namespace SunOne
{
    public class SunOneHelper : IDisposable
    {
        #region Member Variables
 
        public string Server { get; set; }
        public string UserNameConnectString { get; set; }
        private LdapConnection _con;
 
        #endregion
 
        #region Constructor
 
        ///<summary>
        /// construcor
        ///</summary>
        ///<remarks>
        /// Standard LDAP ports:
        ///     LDAP = 389
        ///     LDAP SSL = 636
        ///</remarks>
        public SunOneHelper(string server, string userNameConnectString)
        {
            Server = server;
            UserNameConnectString = userNameConnectString;
        }
 
        #endregion
 
        #region Public Methods
 
        ///<summary>
        /// Cleanup connection if we have one
        ///</summary>
        public void Dispose()
        {
            try
            {
                if (_con != null)
                {
                    _con.Dispose();
                }
            }
            catch
            {
            }
        }
 
        ///<summary>
        /// Validate an LDAP user and password by attempting to log on. 
        ///</summary>
        ///<remarks>
        /// This method catchs log on exceptions,
        /// preferably in DirectoryServicesCOMException but always in the generic
        /// exception below and returns false to indicate that the logon failed. 
        ///</remarks>
        ///<returns>true if logon is successful; otherwise false</returns>
        public bool ValidateUser(string username, string password)
        {
 
            bool authenticated = false;
 
            try
            {
 
                if (_con != null)
                {
                    _con.Dispose();
                    _con = null;
                }
 
                string connectString = UserNameConnectString.Replace("{0}", username);
 
                _con = new LdapConnection(new LdapDirectoryIdentifier(Server));
                _con.AutoBind = false;
                _con.SessionOptions.AutoReconnect = false;
 
                // no ssl, setup the certficate callbacks
                _con.SessionOptions.SecureSocketLayer = false;
                _con.SessionOptions.VerifyServerCertificate = null;
                _con.SessionOptions.QueryClientCertificate = null;
 
                _con.AuthType = AuthType.Basic;
 
                // try the connection
                _con.Bind(new NetworkCredential(connectString, password));
                _con.Dispose();
                authenticated = true;
 
            }
            catch (DirectoryServicesCOMException cex)
            {
                //not authenticated; reason why is in cex        
                authenticated = false;
            }
            catch (Exception ex)
            {
                //not authenticated due to some other exception       
                authenticated = false;
            }
            finally
            {
                _con.Dispose();
                _con = null;
 
            }
            return authenticated;
 
        }
        #endregion
 
    }
}
You can then either deploy this assembly locally to the MVC application, or preferably, install it in the GAC. If you want to install it in the GAC of the Web Server, sign the assembly: 
http://msdn.microsoft.com/en-us/library/ms247123(v=vs.80).aspx
 
And then install it in the GAC of the web server:
 
 
Now all that you have to do is  use this configuration (note that you will need to update the Public Key Token to match your own signed assembly):
    <membershipdefaultProvider="AspNetSunOneMembershipProvider">
      <providers>
        <addname="AspNetSunOneMembershipProvider"
             type="SunOne.AspNetSunOneMembershipProvider,
SunOne, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxx"
             ApplicationName="/myApplication"
             LDAPServer="SunOne.Domain.com"
UserNameConnectString="uid={0}, ou=People, ou=myApplication, dc=myContext,dc=com"/>
      </providers>
    </membership>
 
When determining your connection string, the LDP tool is invaluable when testing your LDAP Bindings. Check it out
It is contained in the Windows XP Service Pack 2 Support tools:

 

Posted on Thursday, June 2, 2011 4:22 PM | Back to top


Comments on this post: Connecting MVC to a SunOne LDAP store by implementing a custom Membership Provider

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © smyers | Powered by: GeeksWithBlogs.net