Tuesday, November 22, 2011

Bypass Forms Authentication to Use Active Directory User Authentication in ASP.NET

Download ActiveDirectoryAuthentication - 297.37 KB

Introduction

This article describes how to use active directory user authentication process on top of forms authentication in asp.netapplication.

User Scenario

Let's say i have a large asp.net application where i have used forms authentication for user authentication process. Suddenly one requirement came up to use an existing active directory for the user authentication process. I found two solutions of it
  • Replace forms authentication with windows authentication
  • Bypass forms authentication to use active directory
 The first option won't work if there is foreign key references exists for aspnet_Users.UserId or aspnet_Membership.UserId column.

Bypass Form Authentication 

 If asp.net login control is used for the user login process then "OnAuthenticate" event can be used to bypass form authentication.    
        protected void LoginUser_Authenticate(object sender, AuthenticateEventArgs e)
        {
            try
            {
                if (IsActiveDirectoryEnabled)
                {
                    if (ActiveDirectoryConnector.IsUserLoggedIn(LoginUser.UserName, LoginUser.Password))
                    {
                        e.Authenticated = true;
                    }
                    else
                    {
                        e.Authenticated = false;
                    }
                }
            }
            catch (Exception ex)
            {
                e.Authenticated = false;
                LoginUser.FailureText = ex.Message;
            }
        }
  
The event is registered in page load event of the login page as 
protected void Page_Load(object sender, EventArgs e)
        {
            if (IsActiveDirectoryEnabled)
            {
                LoginUser.Authenticate += new AuthenticateEventHandler(LoginUser_Authenticate);
            }
        }

Active Directory Settings

The following configuration section has been used to control the active directory connection and user search criteria.
<ldapConfiguration 
    enabled="true" 
    pageLevelSecurityCheck="false" 
    server="192.168.246.128" 
    domain="test.com" 
    directoryPath="DC=test,DC=com" 
    groupName="elixtrauser" 
    filter="(and(objectCategory=person)(objectClass=user)(samaccountname=usertosearch))" 
    filterReplace="usertosearch">    
  </ldapConfiguration>
  •  enabled: this enables active directory authentication process by registering "OnAuthenticate" event in page load of loginpage.
  • pageLevelSecurityCheck: this indicates if the user authentication check needed in every page level or no. 
  • server: LDAP server name or ip address
  • domain: domain name
  • directoryPath: the path of the directory where the users reside.
  • groupName: only the indicated user group will be able to login.
  • filter and filterReplace: used to search user in the specified directory
"ActiveDirectoryConfiguration" class is designed and used to map and use this configuration.

Active Directory Connector

This class talks to active directory and search for user depending on the configuration set in web.config file. "IsUserLoggedIn" method is used for this purpose.
public static bool IsUserLoggedIn(string userName, string password)
        {
            try
            {
                if (ActiveDirectorySettings.Enabled)
                {
                    int startIndex = userName.IndexOf("@");
                    if (startIndex >= 0)
                    {
                        userName = userName.Substring(0, startIndex);
                    }
                    DirectoryEntry ldapConnection = new DirectoryEntry("LDAP://" + ActiveDirectorySettings.Server + "/" + ActiveDirectorySettings.DirectoryPath, userName, password);
                    DirectorySearcher searcher = new DirectorySearcher(ldapConnection);
                    searcher.Filter = ActiveDirectorySettings.Filter.Replace("and", "&");
                    searcher.Filter = searcher.Filter.Replace(ActiveDirectorySettings.FilterReplace, userName);
                    searcher.PropertiesToLoad.Add("memberOf");
                    searcher.PropertiesToLoad.Add("userAccountControl");

                    SearchResult directoryUser = searcher.FindOne();
                    if (directoryUser != null)
                    {
                        int flags = Convert.ToInt32(directoryUser.Properties["userAccountControl"][0].ToString());
                        if (!Convert.ToBoolean(flags & 0x0002))
                        {
                            string desiredGroupName = ActiveDirectorySettings.GroupName.ToLower();
                            if (desiredGroupName!=string.Empty)
                            {
                                desiredGroupName = "cn=" + desiredGroupName + ",";
                                int numberOfGroups = directoryUser.Properties["memberOf"].Count;
                                bool isWithinGroup = false;
                                for (int i = 0; i < numberOfGroups; i++)
                                {
                                    string groupName = directoryUser.Properties["memberOf"][i].ToString().ToLower();
                                    if (groupName.Contains(desiredGroupName))
                                    {
                                        isWithinGroup = true;
                                        break;
                                    }
                                }
                                if (!isWithinGroup)
                                {
                                    throw new Exception("User [" + userName + "] is not a member of the desired group.");
                                }
                            }
                            return true;
                        }
                        else
                        {
                            throw new Exception("User [" + userName + "] is inactive.");
                        }
                    }
                    else
                    {
                        throw new Exception("User [" + userName + "] not found in the specified active directory path.");
                    }
                }
                else
                {
                    return true;
                }
            }
            catch (LdapException ex)
            {
                if (ex.ErrorCode == 49)
                {
                    throw new Exception("Invalid user authentication. Please input a valid user name & pasword and try again.",ex);
                }
                else
                {
                    throw new Exception("Active directory server not found.", ex);
                }
            }
            catch (DirectoryOperationException ex)
            {
                throw new Exception("Invalid active directory path.", ex);
            }
            catch (DirectoryServicesCOMException ex)
            {
                if (ex.ExtendedError == 8333)
                {
                    throw new Exception("Invalid active directory path.", ex);
                }
                else
                {
                    throw new Exception("Invalid user authentication. Please input a valid user name & pasword and try again.", ex);
                }
            }
            catch (System.Runtime.InteropServices.COMException ex)
            {
                throw new Exception("Active directory server not found.", ex);
            }
            catch (ArgumentException ex)
            {
                if (ex.Source == "System.DirectoryServices")
                {
                    throw new Exception("Invalid search filter expression.", ex);
                }
                else
                {
                    throw new Exception("Unhandeled exception occured while authenticating user using active directory.", ex);
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Unhandeled exception occured while authenticating user using active directory.", ex);
            }
        }
This method is used in "OnAuthenticate" event by passing the user name and password to check the existence of the user and if the user has the access right. If it's true then a forms authentication ticket is issued and registered in cookie to create the userlogin session.

No comments: