This logic can be used to give you a SINGLE SIGN-ON / AUTOLOGIN solution with ASP.NET 2.0 or higher using a combination of authentication providers on IIS 5.1 (Windows 2000). The demo is using C# and does require a network administrator account to make the magic work. I've tried using other accounts, but was only able to get this to work with a "Domain Administrator" account.
I tried so many solutions (applicaiton sub folder using Windows auth, web service, every possible IIS 5.1 settings, WMI query) and nothing worked. After trying to figure this out for a few weeks, I realized that I had a single sign-on solution being used for our SonicWall content filtering system appliances. I looked into how this was being done and I decided to put the same logic in the page_load event of my login control.
After an hour of playing around, I had a working ASP.NET single sign-on solution. Using impersonation, I create a thread that uses a domain administrator account (this is a requirement, which is also required by the SonicWall solution) to perform a remote call to the client workstation to obtain the currently logged on user(s). The magic is done by using "netapi32.dll", by passing the NetWkstaUserEnum method a hostname your able to obtain an array of users logged into the computer. I always take the last user in the list, since the machine account "domain\machinename$" is always first. I've not had it return the wrong person yet. Even if a user is not automatically logged in, they can still manually enter their login/password to access the site.
** On my development machine, when I checked the array results I would see a bunch of accounts that were generated each time I ran VS. Each time I ran my demo applicaiton, I'd see a different account like (ASPNET) as the last user. I think this has something to do with the thread VS users for the built-in webserver.
Too keep my original Forms Authentication solution in place, I parse the account (e.g. DOMAIN\User => User) and I check the Forms Authentication database for the user's name, skipping the password check. If they exist, I creates a FormsAuthenticationTicket and the process is complete.
** This code is part of a C#, ASP.NET Website Project. I've tested this code on a few websites all using C# and this does work. The cavaet of requiring a high-security account is a pain but it works. If anybody knows of another way to pull this off, let me know...
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack) { // Attempt Autologin
string account = String.Empty;
string domain = String.Empty;
string user = String.Empty;
string email = String.Empty;
try
{
ImpersonateUser iu = new ImpersonateUser();
if (iu.impersonateValidUser("<DOMAIN ACCOUNT>", "<DOMAIN NAME>", "<DOMAIN ACCOUNT PASSWORD>"))
{
NetWorkstationUserEnum nws = new NetWorkstationUserEnum();
string host = nws.DNSLookup(Request.UserHostAddress);
string[] users = nws.ScanHost(host);
if (nws.ScanHost(host).Length > 0)
{
account = users[users.Length - 1];
domain = account.Substring(0, account.IndexOf("\\"));
user = account.Substring(account.IndexOf("\\") + 1, account.Length - account.IndexOf("\\") - 1);
}
iu.undoImpersonation();
}
}
catch (Exception logex)
{
Log.Debug("Autologin Failure: " + logex.Message);
}
if (!String.IsNullOrEmpty(account))
{
Log.Info("Account: " + account);
if (domain.ToUpper() == "<DOMAN NAME>")
{
email = user.ToLower() + "@<DOMAIN ACCOUNT EMAIL>";
String strRole = AssignRoles(email);
if (!String.IsNullOrEmpty(strRole))
{
FormsAuthentication.Initialize();
//The AddMinutes determines how long the user will be logged in after leaving
//the site if he doesn't log off.
FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1,
email, DateTime.Now,
DateTime.Now.AddDays(7), true, strRole,
FormsAuthentication.FormsCookiePath);
HttpCookie ck;
ck = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(fat));
ck.Expires = fat.Expiration;
ck.Path = FormsAuthentication.FormsCookiePath;
Response.Cookies.Add(ck);
Response.Redirect(FormsAuthentication.GetRedirectUrl(email, true));
}
else
{
Log.Info("Unable to create FAT, user does not have any roles assigned.");
}
}
}
}
}
By default, the login control is only displayed when a user is not authenticated. This is becaus the auto login logic is hooked onto the modules load event. This solution is currently working on Windows 2000 with IIS 5.1, for a portal developed with ASP.NET 2.0 and C#. The portal uses forms authentication but existing on an internal domain. The site has a a few hundred users, some are remote agents that connect via VPN and are not part of the domain.
This has made a huge difference, since we now set the company default webpage to the site and key that they'll be able to see everything since we've automatically logged them in. The first day I made this change, I got 3 emails about the some major changes users saw done to the site. I found this comment really funny, since we didn't make any changes to the content... it just so happened that this was the first time they had ever logged in!
Good References for the soltuion:
http://www.codeproject.com/KB/IP/LoggedOnUsersPart2.aspx (had a good note about security)
Other Notes:
I found another soltuion saying that a WMI Query should be able to obtain the same data, but when I tried this using impersontation it did not work. I read that WMI queries, even when run via Impersation, are limited by the ASPNET WP thread. I can't explan the details but I did find two posts about this and took them at face value.
You can download the C# ASP.NET code here. This is an excerpt from my project but it's working code. If anybody else has a working soltuion that does not require a domain admin acount, let me know. Having a Single Sign-On ("SSO") solution for an ASP.NET intranet site is awesome!
FormsWindowsMixxed_Source.zip (1.99 KB)