As IT professionals we are always dumbfounded when we see a user who has passwords written on sticky notes and then used to decorate the edges of a monitor. We are even guilty of keeping track of some passwords in a spreadsheet from time to time, which is also not a security best practice, and we don't even like to think about people who really just have one or two passwords they use for everything. Why do we do this? Too many passwords. I currently have around 40 passwords I have to remember for various software, networks, and web sites.
Most of my time is spent developing and maintaining my company's intranet/extranet, and I have it set up to use a hybrid forms/windows authentication. That way if the user is already signed into a computer on our domain, the site will pull their windows credentials, match it up with a user in the database, and automatically log them in through forms authentication. This happens in less than a second and the user never knows it happened. But, users outside the company (that aren't signed into our network) can use the classic username/password form and gain access to the site without needing a user account in Active Directory.
However, there are times when an employee who typically access the site from inside the network need to look at something from home or on the road. Since they aren't signed into the domain, the site won't automatically pull their windows credentials ... so they are sent to the classic forms authentication log in page. What credentials should they try to use? Well, we could give them one more username/password to keep track of ... or the solution I went for is if the credentials don't match up with any forms authentication user in the database, query active directory from code to see if the credentials the user provided are valid domain credentials. So in reality, on the classic forms authentication page the user could enter forms authentication credentials (validated against the database), or enter domain credentials (validated against Active Directory).
There are other solutions out there (like this one) that allow you to use forms authentication with Active Directory, but really the code that I needed was very simple. It is based on a snippet I found in Developing More-Secure Microsoft ASP.NET 2.0 Applications. It uses LDAP queries to authenticate a given set of credentials against Active Directory, but creating a DirectoryEntity object with those credentials and then forcing them to bind. If it returns an error code of 2147023570, that indicates a login failure ... which means the credentials the user provided are not valid domain credentails. If no error occurs, that means the credentials matched a user in Active Directory.
/// <summary>
/// Returns true if the given credentials match a valide forms authentication user in the database or an account in Active Directory,
/// and false otherwise.
/// </summary>
public static bool ValidateCredentials(string Username, string Password)
{
bool IsValid;
using (SqlConnection thisConnection = new SqlConnection(Common.ConnectionString))
{
string SprocName = "sproc_ValidateCredentials";
SqlCommand thisCommand = new SqlCommand(SprocName, thisConnection);
thisCommand.CommandType = CommandType.StoredProcedure;
thisCommand.Parameters.AddWithValue("@Username", Username);
thisCommand.Parameters.AddWithValue("@Password", Password);
thisConnection.Open();
IsValid = Convert.ToBoolean(thisCommand.ExecuteScalar());
}
if (!IsValid)
IsValid = AuthenticateAgainstActiveDirectory(Username, Password);
return IsValid;
}
/// <summary>
/// Returns true if the given UserID and Password were valid network credentials according to Active Directory, and false otherwise.
/// The code uses LDAP to communicate with Active Directory, and simply creates a DirectoryEntry object using the given credentials
/// and then takes some action that causes a bind. If no error occurs, the user would be allowed to log onto the domain.
/// </summary>
public static bool AuthenticateAgainstActiveDirectory(string Username, string Password)
{
// Strip everything but the Username out from the text the user provided
Username = Username.ToLower().Replace(@"DomainName\", "");
// Create the entity that will connect to the LDAP server
DirectoryEntry thisEntry = new DirectoryEntry(@"LDAP://192.168.0.1", Username, Password);
try
{
// Perform an action that will force the bind to ActiveDirectory ... if this doesn't throw
// an error then a user would be able to log onto the network with the given credentials
thisEntry.RefreshCache();
return true;
}
catch (System.Runtime.InteropServices.COMException thisExc)
{
// Make sure the error that got thrown was a login failure, and if it wasn't rethrow the error
if (thisExc.ErrorCode != -2147023570)
throw;
return false;
}
}