Click here to Skip to main content
Click here to Skip to main content

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

, 9 Oct 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
This article describes how to keep form based and active directory user based authentication process in parallel in ASP.NET.

Introduction

This article describes how to use active directory user authentication process on top of forms authentication in ASP.NET application.

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 foreign key references exist 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 login page.
  • pageLevelSecurityCheck: This indicates if the user authentication check is 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 searches 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 & password 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 & password 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("Unhandled exception occurred 
			while authenticating user using active directory.", ex);
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Unhandled exception occurred 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 user login session.

Conclusion

So user authentication from active directory can be achieved easily on top of form authentication.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Fazlur Rahman
Software Developer (Senior) MediaNet Group
United Arab Emirates United Arab Emirates
I am Bachelor in CSE from KUET,Bangladesh. I have more than 6 years experience in ASP.NET and C# and currently working in a software company in Dubai,UAE as a Senior Software Engineer. I am MCAD(Microsoft Certified Application Developer) certified since 2005. Please feel free to contact with me at nill_akash_7@yahoo.com.


Comments and Discussions

 
BugIssues PinmemberBrian Desmond [MVP]9-Oct-11 10:23 
There are a number of issues with this that don't make it particuarly tenable as a solution in an enterprise environment:
 
1. Specifying an IP address and bypassing DC Locator (your server property) is a bad idea. It's not necessary at all if the server is in the domain.
 
2. Your sample filter won't actually work - the operator is "&" not "and" although I see you are replacing this inyour code. Presumably any attribute or token which has and as a substring will get mangled, though.
 
3. The assumption that the samAccountName always matches the left side of a userPrincipalName (your search for @) is pure coincidence by way of some customers implementing it this way.
 
4. Your group membership expansion doesn't handle nested groups and is entirely dependent on the substring in the config (groupName) being in the distinguished name of the object. This isn't the actual group name that Windows would print out on token expansion, and if by chance the group name is also in the name of a parent object in the DN, you'll have a false positive.
 
The best way to calculate group membership here is with the tokenGroups attribute.
 
5. You've misspelled "password" and "unhandled" in all your exceptions.
 
6. As a general suggestion, assuming you're using .Net 3.5 or better, you may find the S.DS.AccountManagement namespace to be a better fit as it abstracts much of the knowledge requirements around AD.
GeneralRe: Issues Pinmembernullpointer12-Aug-13 17:49 
GeneralRe: Issues Pinmembercharbaugh4-Dec-13 20:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 9 Oct 2011
Article Copyright 2011 by Fazlur Rahman
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid