Introduction
The necessity came in when we had a Web Application that is intended to be used by Client's users (customers). They have logged into their network and are authenticated by their network. The Client�s intranet will have a link to access the Web application. When they click the link, they might be asked for User Name and Password. The web application has its own Login/password page, if you are not authenticated by this application network domain (Active Directory roles). But the Client would not like that as they need to remember four or five different username/passwords for every provider application that they need to use from their intranet. They want to access the application when they click a link from their intranet. When they access the same website from outside their intranet, it should prompt for the login.
Available Solutions:
- Use Passport Authentication (again the client users should be creating a passport account) and the web application should be registered in passport to use their service.
- Use SAML based Authentication Modules that allow Federated Security Authentication. This requires lot of work.
- Use a Security Key provider module that generates a Ticket to send it along with the user credentials from the client side. The Web application will use the same provider module to validate the Key and authenticate the login.
- Use a HTTP POST to send Identity values along with a Hash Value (credentials + private key) to the Web application. The application creates the Hash value using the credentials and private key, matches the passed Hash value, and authenticates the login attempt.
Solution:
I am going for the fourth solution, as it is the cheapest method. It may not be the best but it is good over SSL connections. However, the solution needs some work at the client side to pass the values. The client code will pass four parameters using POST to the application Pass-Through Page: Client ID, Username, Timeline, and Key Hash. The Client ID is a number to identify a client (valid in the Web application), the Username may be the user�s network login username, Timeline is a date time in �yyyymmddHHss� GMT/UTC time format, and Key Hash is the hashed (SHA1) value of Client ID + User Name + Time line + Private Key. The private key will be the same key on both sides and there is a small compromise on this � keys will be securely stored in both applications (database or a secured file location on network) in encrypted form.
The POST values are received in the web application, validates if the Timeline is within the allowable window (+/- 2 minutes) and the application gets the copy of Private Key from the database, uses that key to create a Hash value of the Client ID, user name, timeline and the private key. This hash value is compared to the Hash key POSTed. If both values match, it allows the user to access the application by setting the over-ride login.
The important thing to remember is this works only if it is accessed over SSL. If not, anyone could sniff the Key hash and hack the session. However, they may not be able to get any other user�s data by getting the Hash Value. SHA1 is very hard to decrypt. Also, at the client side POST code, it should not take more time to create the POST URL and re-direct � otherwise it allows the user to use 'View Source' and get the Hash value. This may not be useful anyways but it is a possibility to find the Hashed value.
Source Code:
Most of the code is self explanatory and I have given the purpose of each project code.
- WebConfig:
<appSettings>
<add key="PassThroughAccountId" value="1"/>
<add key="URLForward"
value="https://localhost/passthroughapp/PassThrough.aspx"/>
<add key="Key" value="WAga1weKy7Tcbonly1Af76jbj7jbb"/>
</appSettings>
- Create Pass-through Values.
In the project TestPassThrough, PassthroughTest.aspx.cs contains the code to generate the Hash values based on the credentials that is entered in the textboxes. Basically, I have used this method just to test the application. In production application, you might have to get the user details from the Network login credentials.
private void Page_Load(object sender, System.EventArgs e)
{
KeyToHash = ConfigurationSettings.AppSettings.Get("Key");
TextBoxKey.Text=KeyToHash.ToString();
if (!Page.IsPostBack)
{
txtTimeline.Text = DateTime.UtcNow.ToString("yyyyMMddHHmmss");
Submit.Disabled = true;
}
}
private void Submit_ServerClick(object sender, System.EventArgs e)
{
string toSend;
toSend = "PostToAppl.aspx" + "?var1=" + txtUsername.Text +
"&var2=" + txtTimeline.Text + "&var3=" +
TextBoxKey.Text + "&var4=" + txtClientId.Text;
Submit.Disabled = true;
HttpContext.Current.Response.Redirect(toSend);
}
private void GenHash_Click(object sender, System.EventArgs e)
{
string Username = txtUsername.Text;
string Timeline = txtTimeline.Text;
string SentHash = Username + Timeline + txtClientId.Text + KeyToHash;
string base64HashValue =
FormsAuthentication.HashPasswordForStoringInConfigFile(SentHash,
"SHA1");
TextBoxKey.Text = base64HashValue;
Submit.Disabled = false;
}
private void GetUTCTime_Click(object sender, System.EventArgs e)
{
txtTimeline.Text = DateTime.UtcNow.ToString("yyyyMMddHHmmss");
}
- Post to the application:
I have sent the values in Query string to another aspx page (which automatically POSTs the values to the application). I did this to verify the values being sent. You can avoid by doing in a single step.
private void Page_Load(object sender, System.EventArgs e)
{
Username_Form = Request.QueryString.Get(0);
Timeline_Form = Request.QueryString.Get(1);
Hash_Form = Request.QueryString.Get(2);
ClientId = Request.QueryString.Get(3);
URLForward = ConfigurationSettings.AppSettings.Get("URLForward");
}
//Code Behind :
<%@ Page language="c#" Codebehind="PostToAppl.aspx.cs"
AutoEventWireup="false" Inherits="Passthrough.UI.Web.PostToAppl" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>TestPostForm</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 MS_POSITIONING="GridLayout" onload="Redirect();">
<form action='<%=URLForward%>' method='post'
Target='_blank' name='frmLogon'>
<table border='1'>
<tr>
<td class='bold'>UserName</td>
<td> </td>
<td><input type='text' name='Username' value='<%=Username_Form%>'
size='30' MAXLENGTH='30' readOnly>
</td>
</tr>
<tr>
<td class='bold'>Timeline</td>
<td> </td>
<td><input type='text' name='Request Time'
value='<%=Timeline_Form%>'
size='14' MAXLENGTH='14' readOnly></td>
</tr>
<tr>
<td class='bold'>Hash</td>
<td> </td>
<td><input type='text' name='Hashed Value'
value='<%=Hash_Form%>' size='50'
MAXLENGTH='50' readOnly></td>
</tr>
<tr>
<td class='bold'>ClientId</td>
<td> </td>
<td><input type='text' name='Hashed Value'
value='<%=CleintId%>' size='50'
MAXLENGTH='50' readOnly></td>
</tr>
<tr>
<td colspan='3'></td>
</tr>
</table>
<script language="JavaScript">
function Redirect()
{
document.frmLogon.submit();
}
</script>
</form>
</body>
</HTML>
- Application:
Application code receives the posted values and gets the private key from the database or Web.config (for now, I have hard coded the key in the code itself), generates the HASH value, and compares it with the supplied value. If it is a match, set the User Object in the Session and allow the application to proceed.
Happy Coding.
Conclusion
Any suggestions are welcome, this is a part of an edited application. It may contain some incomplete or commented code as my intention was to explain and give an outline of the Pass-Through authentication. Any other better methods may be available. So use it with caution and explore all possibilities to prevent the sessions hacked.