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.