Background
Last month I worked on a small assignment to authenticate windows account (Domain or Local) using form authentication. The purpose of this task was to facilitate our application users to login with any valid windows account (instead of automatic authentication of windows logged in user).
As it was an interesting task, I decided to share my experience with you.
Requirement
The application should authenticate windows user using form authentication so that the currently logged in user shouldn't be bound to logged-in in the application only with his windows account. He should be able to log-in with any valid windows account.
Solution
We need to do the following steps to get the desired functionality:
- Configure Authorization and Authentication settings in web.config
- A login page and execute logic to authenticate provided credential of windows user
- If provided credentials are authenticated in step 2, then generate an authentication token so that user should be able to navigate into the authorized pages of your application.
1. Configure Authorization and Authentication Settings in web.config
We need to use Form authentication. The user will enter his windows credential in the form and we will validate provided windows credential using custom logic in step 2.
<authentication mode="Forms">
<forms loginUrl="login.aspx" name=".ASPXFORMSAUTH"></forms>
</authentication>
To restrict anonymous access, you need to make the following authorization settings in web.config:
<authorization>
<deny users="?"/>
</authorization>
2. Create a Login Page and Execute Logic to Authenticate Provided Credential of Windows User
We need to create a login page (e.g. login.aspx) to get username and password information from user and then validate them. We can have different options to validate windows credentials, the way I chose is LogonUser()
method of a win32 API called Advapi32.dll.
The LogonUser
function attempts to log a user on to the local computer. This method takes username, password and other information as input and returns a Boolean value to indicate that either user is logged or not. If it returns true
, it means the provided username and password are correct. To use this method in our class, we need to include the following namespace:
using System.Runtime.InteropServices;
And the add method declaration with DLLImport
attribute (as this is a method of Win32 DLL which is an unmanaged DLL).
[DllImport("ADVAPI32.dll", EntryPoint =
"LogonUserW", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool LogonUser(string lpszUsername, string lpszDomain,
string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
According to the MSDN documentation:
lpszUsername [in]
A pointer to a null
-terminated string
that specifies the name of the user. This is the name of the user account to log on to. If you use the user principal name (UPN) format, User@DNSDomainName, the lpszDomain
parameter must be NULL
.
lpszDomain [in, optional]
A pointer to a null
-terminated string
that specifies the name of the domain or server whose account database contains the lpszUsername
account. If this parameter is NULL
, the user name must be specified in UPN format. If this parameter is ".
", the function validates the account by using only the local account database.
lpszPassword [in]
A pointer to a null
-terminated string
that specifies the plaintext password for the user account specified by lpszUsername
. When you have finished using the password, clear the password from memory by calling the SecureZeroMemory
function. For more information about protecting passwords, see Handling Passwords.
dwLogonType [in]
The type of logon operation to perform.
dwLogonProvider [in]
Specifies the logon provider.
phToken [out]
A pointer to a handle variable that receives a handle to a token that represents the specified user.
3. Generate an Authentication Token, If Provided Credentials are Authenticated in step 2
If provided credentials are authenticated by LogonUser()
method, then we need to generate an authentication token so that users should be able to navigate into the authorized pages of the application.
FormsAuthentication.RedirectFromLoginPage()
Or:
FormsAuthentication.SetAuthCookie()
can be used for this purpose.
Here is login button’s Click handler code for authentication and generating authentication token. The comments will help you to understand the code.
protected void btnLogin_Click(object sender, EventArgs e)
{
string domainName = GetDomainName(txtUserName.Text);
string userName = GetUsername(txtUserName.Text);
IntPtr token = IntPtr.Zero;
bool result = LogonUser(userName, domainName,
txtPassword.Text, 2, 0, ref token);
if (result)
{
if (string.IsNullOrEmpty(Request.QueryString["ReturnUrl"]))
{
FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, false);
}
else
{
FormsAuthentication.SetAuthCookie(txtUserName.Text, false);
Response.Redirect("default.aspx");
}
}
else
{
Response.Write("Invalid username or password.");
}
}
Let's Put It All Together
Login.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Login.aspx.cs"
Inherits="Login" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Windows Authentication Using Form Authentication</title>
<style type="text/css">
.style1
{
width: 100%;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<div>
<table class="style1">
<tr>
<td>
<asp:Label ID="lblUserName" runat="server" Text="User Name:">
</asp:Label>
</td>
<td>
<asp:TextBox ID="txtUserName" runat="server"></asp:TextBox>
</td>
</tr>
<tr>
<td>
<asp:Label ID="lblPassword" runat="server" Text="Password:">
</asp:Label>
</td>
<td>
<asp:TextBox ID="txtPassword" runat="server" TextMode="Password" >
</asp:TextBox>
</td>
</tr>
<tr>
<td>
</td>
<td>
<asp:Button ID="btnLogin" runat="server" onclick="btnLogin_Click"
Text="Login" />
</td>
</tr>
</table>
</div>
<p>
</p>
</form>
</body>
</html>
Login.aspx.cs
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Runtime.InteropServices;
public partial class Login : System.Web.UI.Page
{
[DllImport("ADVAPI32.dll", EntryPoint =
"LogonUserW", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool LogonUser(string lpszUsername,
string lpszDomain, string lpszPassword, int dwLogonType,
int dwLogonProvider, ref IntPtr phToken);
public static string GetDomainName(string usernameDomain)
{
if (string.IsNullOrEmpty(usernameDomain))
{
throw (new ArgumentException("Argument can't be null.", "usernameDomain"));
}
if (usernameDomain.Contains("\\"))
{
int index = usernameDomain.IndexOf("\\");
return usernameDomain.Substring(0, index);
}
else if (usernameDomain.Contains("@"))
{
int index = usernameDomain.IndexOf("@");
return usernameDomain.Substring(index + 1);
}
else
{
return "";
}
}
public static string GetUsername(string usernameDomain)
{
if (string.IsNullOrEmpty(usernameDomain))
{
throw (new ArgumentException("Argument can't be null.", "usernameDomain"));
}
if (usernameDomain.Contains("\\"))
{
int index = usernameDomain.IndexOf("\\");
return usernameDomain.Substring(index + 1);
}
else if (usernameDomain.Contains("@"))
{
int index = usernameDomain.IndexOf("@");
return usernameDomain.Substring(0, index);
}
else
{
return usernameDomain;
}
}
protected void btnLogin_Click(object sender, EventArgs e)
{
string domainName = GetDomainName(txtUserName.Text);
string userName = GetUsername(txtUserName.Text);
IntPtr token = IntPtr.Zero;
bool result = LogonUser(userName, domainName, txtPassword.Text, 2, 0, ref token);
if (result)
{
if (string.IsNullOrEmpty(Request.QueryString["ReturnUrl"]))
{
FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, false);
}
else
{
FormsAuthentication.SetAuthCookie(txtUserName.Text, false);
Response.Redirect("default.aspx");
}
}
else
{
Response.Write("Invalid username or password.");
}
}
}
And We Are DONE!!!
We are done with authentication of windows account using form authentication.
Feedback
You feedback will be very helpful for me. You can send me an email at akhhttar@gmail.com. Thanks!
I am Microsoft Certified Technology Specialist for Web Application Development. I have 4 year experience of Web and Distributed application development.I have considerable experience developing client / server software for major corporate clients using the Windows operating systems and .NET platform ( ASP.NET, C# , VB.NET).I have single and multi-threaded code development experience, as well as experience developing database and enterprise level distributed applications.