Thursday, September 6, 2007

Achieving IWA for Internet based web app

Here, I present you with a sample .NET application on how to authenticate users with your domain instead of application level user id and password. The benefit of doing is that all your internet based web apps can use the same mechanism and NOT store individual user ids & passwords in a seperate datastore (often times different for each app). This feature allows your administrators to control users enterprise-wide instead of worrying about controlling users at application level & making sure all company security guidelines are followed.

Environment : VS 2005, C# , IIS

Login.aspx is your application's start page in this example. If the user is not authenticated, he gets an error message, otherwise the user proceeds to default.aspx page of the app.

LoginPage.aspx:


LoginPage.aspx with Incorrect Password:


LoginPage.aspx with Correct Password:


Default.aspx after Authentication done:


You can use the following code to achieve this :

===================
LoginPage.aspx
===================
<%@ Page language="c#" Inherits="LogonUserCS.LoginPage" CodeFile="LoginPage.aspx.cs" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>LoginPage</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body>
<FORM id="Form1" method="post" runat="server">
<TABLE id="Table1" cellSpacing="1" cellPadding="1" width="300" border="0">
<TR>
<TD><FONT face="Arial"><STRONG>Username:</STRONG></FONT></TD>
<TD>
<asp:TextBox id="txtUsername" runat="server" Width="150px">UserID</asp:TextBox><FONT face="Arial"></FONT></TD>
</TR>
<TR>
<TD><FONT face="Arial"><STRONG>Password:</STRONG></FONT></TD>
<TD>
<asp:TextBox id="txtPassword" runat="server" Width="150px" TextMode="Password">tryme</asp:TextBox><FONT face="Arial"></FONT></TD>
</TR>
<TR>
<TD colSpan="2"><FONT face="Arial">
<asp:CheckBox id="chkRemember" runat="server" Text="Remember login information"></asp:CheckBox></FONT></TD>
</TR>
</TABLE>
<P><FONT face="Arial" color="#ff0000"><STRONG>
<asp:Label id="lblError" runat="server" Visible="False">Login failed! Please try again.</asp:Label>
</STRONG></FONT></P>
<P>
<asp:Button id="cmdLogin" runat="server" Text="Login" onclick="cmdLogin_Click"></asp:Button><BR>
</P>
</FORM>
</body>
</HTML>


=======================
LoginPage.aspx.cs
=======================
using System;
using System.Collections;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Configuration;

namespace LogonUserCS
{
/// <summary>
/// Summary description for LoginPage.
/// </summary>
public partial class LoginPage : System.Web.UI.Page
{

#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
}
#endregion

// Declare the logon types as constants
const long LOGON32_LOGON_INTERACTIVE = 2;
const long LOGON32_LOGON_NETWORK = 3;

// Declare the logon providers as constants
const long LOGON32_PROVIDER_DEFAULT = 0;
const long LOGON32_PROVIDER_WINNT50 = 3;
const long LOGON32_PROVIDER_WINNT40 = 2;
const long LOGON32_PROVIDER_WINNT35 = 1;

[DllImport("advapi32.dll",EntryPoint = "LogonUser")]
private static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);

/// <summary>
/// Validates the user based on the supplied credentials
/// </summary>
/// <param name="Username">The username to use when valdiating the user</param>
/// <param name="Password">The password to use when validating the user</param>
/// <param name="Domain">The account domain or machine name to use when validating the user</param>
/// <returns>Returns true if the credentials are valid, and false otherwise</returns>
private bool ValidateLogin(
string Username,
string Password,
string Domain)
{
// This is the token returned by the API call
// Look forward to a future article covering
// the uses of it
IntPtr token = new IntPtr(0);
token = IntPtr.Zero;

// Call the API
if (LogonUser(
Username,
Domain,
Password,
(int)LOGON32_LOGON_NETWORK,
(int)LOGON32_PROVIDER_DEFAULT,
ref token))
{
//' Since the API returned TRUE, return TRUE to the caller
return true;
}
else
{
//' Bad credentials, return FALSE
return false;
}
}

/// <summary>
/// Called when the user clicks on the Login button
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void cmdLogin_Click(object sender, System.EventArgs e)
{
string Username = txtUsername.Text;
string Password = txtPassword.Text;
// Pull the domain out of the web.config file
string Domain = ConfigurationSettings.AppSettings["AccountDomain"];

if (ValidateLogin(Username,Password,Domain))
{
//' Since the credentials are valid,
//' redirect the user to the calling page
FormsAuthentication.RedirectFromLoginPage(Username,chkRemember.Checked);
}
else
{
//' Bad credentials, show an error message
lblError.Visible = true;
}

}
}
}

========================
default.aspx
========================
<%@ Page language="c#" Inherits="LogonUserCS.WebForm1" CodeFile="default.aspx.cs" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>WebForm1</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body>
<FORM id="Form1" method="post" runat="server">
<FONT face="Arial">You are logged in as </FONT><FONT face="Arial"><STRONG>
<asp:Label id="lblUser" runat="server"></asp:Label></STRONG></FONT><FONT face="Arial">.</FONT>
</FORM>
</body>
</HTML>

============================
default.aspx.cs
============================

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace LogonUserCS
{
/// <summary>
/// Summary description for WebForm1.
/// </summary>
public partial class WebForm1 : System.Web.UI.Page
{

protected void Page_Load(object sender, System.EventArgs e)
{
lblUser.Text = User.Identity.Name;
}

#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{

}
#endregion
}
}

========================
web.config
========================
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation defaultLanguage="C#" debug="true">
<compilers>
<compiler language="c#" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" extension=".cs" compilerOptions="/d:DEBUG;TRACE"/></compilers></compilation>
<customErrors mode="On"/>
<authentication mode="Forms">
<forms loginUrl="/LoginPage.aspx" name="LogonUserDemo" timeout="20" path="/" protection="All"/>
</authentication>
<authorization>
<allow users="*"/>
<deny users="?"/>
</authorization>
<trace enabled="false" requestLimit="10" pageOutput="false" traceMode="SortByTime" localOnly="true"/>
<sessionState mode="InProc" stateConnectionString="tcpip=127.0.0.1:42424" sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes" cookieless="false" timeout="20"/>
<globalization requestEncoding="utf-8" responseEncoding="utf-8"/>
<xhtmlConformance mode="Legacy"/></system.web>
<appSettings>
<add key="AccountDomain" value="DomainName"/>
</appSettings>
</configuration>

No comments: