Securing Login Page and Maintaining Single Session Per User in ASP.Net Application

This article is an extension to my previous articles:

  1. Generation of CAPTCHA Image Using Generic Handler for Login Page
  2. Stored Procedure For Login Page and Custom Error Handling

1. Filtering from login page

If the filtering of the XSS attack occurred from the login page then:

  • Cross-site scripting (XSS): Cross-site scripting is nothing but injection of client-side scripts into a website. These scripts can be HTML scripts or JavaScript scripts. Cross-site scripting can be performed by passing scripts in the form of a TextBox (input controls), query strings, cookies, session variables and application variables.
  • Anti XSS remedy: the HtmlEncode function located in the Microsoft's AntiCross-Site Scripting Library. In contrast with the Server.HtmlEncode and HttpUtility.HtmlEncode functions, the later function takes a more aggressive approach by using a white-list filtering instead of a black-list, hence more PCI standards-compliant and more secure.
  • Using FilteredTextBox Extender: Using this extender the user is restricted to enter valid data as input and the chances of injecting a client script or database related script is diminished.

Input controls should have a filtertextbox extender as below.

  1. <asp:TextBox ID="txtLogin" runat="server" Width="175px" MaxLength="20" AutoCompleteType="Disabled"></asp:TextBox>  
  2. <asp:FilteredTextBoxExtender ID="fltr_txtLogin" runat="server" FilterType="UppercaseLetters,LowercaseLetters,Numbers" TargetControlID="txtLogin">  
  3. </asp:FilteredTextBoxExtender>  
  4.   
  5. <asp:TextBox ID="txtPassword" runat="server" Width="175px" MaxLength="20" AutoCompleteType="Disabled" TextMode="Password"></asp:TextBox>  
  6. <asp:FilteredTextBoxExtender ID="fltr_txtPassword" runat="server" FilterType="Numbers, LowercaseLetters, UppercaseLetters, Custom" TargetControlID="txtPassword" ValidChars="@!_%$#"></asp:FilteredTextBoxExtender>  
  7.   
  8. <asp:TextBox ID="txtCode" runat="server" Width="175px" MaxLength="5" AutoCompleteType="Disabled"></asp:TextBox>  
  9. <asp:FilteredTextBoxExtender ID="fltr_txtCode" runat="server" FilterType="Numbers"  
  10. TargetControlID="txtCode"></asp:FilteredTextBoxExtender> 

2. Alert user if other session is active on another machine for requested credentials.

And

3. Having single session per user means at a time only one session will be active for each user.

If someone has logged in from another machine using the same credentials then the user must be alerted about another active session on the credentials provided.



To maintain a single session, we have already added two columns in UserMaster. The IsLogin flag maintains a user's Login Status and UserSession column maintains a unique session id. If the user selects the OK button then a new session id is updated against the user details in UserMaster else returned to the login page.

Modified Login page aspx script

  1. <%@ Page Title="LOGIN" Language="C#" AutoEventWireup="true" CodeFile="Login.aspx.cs"  
  2.     Inherits="Login" %>  
  3.   
  4. <%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="asp" %>  
  5. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  6. <head id="Head1" runat="server">  
  7.     <title>LOGIN</title>  
  8. </head>  
  9. <body>  
  10.     <form id="form1" runat="server">  
  11.     <h1>  
  12.         Login</h1>  
  13.     <table id="tblLogin" runat="server" width="40%" border="0" cellpadding="0" cellspacing="4"  
  14.         style="background-color: #cecece;" align="center">  
  15.         <tbody>  
  16.             <tr>  
  17.                 <td align="center" colspan="2">  
  18.                     <asp:Label ID="lblError" runat="server" ForeColor="Red"></asp:Label>  
  19.                     <asp:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">  
  20.                     </asp:ToolkitScriptManager>  
  21.                 </td>  
  22.             </tr>  
  23.             <tr>  
  24.                 <td width="30%" align="right">  
  25.                     User ID :  
  26.                 </td>  
  27.                 <td width="70%">  
  28.                     <asp:TextBox ID="txtLogin" runat="server" Width="175px" MaxLength="20" AutoCompleteType="Disabled"></asp:TextBox>  
  29.                     <asp:RequiredFieldValidator ID="req_txtLogin" runat="server" ErrorMessage="Enter Login ID."  
  30.                         ToolTip="Enter Login ID." ControlToValidate="txtLogin" Text="*" Display="Static"  
  31.                         ForeColor="Red" ValidationGroup="vldLogin"></asp:RequiredFieldValidator>  
  32.                     <asp:FilteredTextBoxExtender ID="fltr_txtLogin" runat="server" FilterType="UppercaseLetters,LowercaseLetters,Numbers"  
  33.                         TargetControlID="txtLogin">  
  34.                     </asp:FilteredTextBoxExtender>  
  35.                 </td>  
  36.             </tr>  
  37.             <tr>  
  38.                 <td align="right">  
  39.                     Password :  
  40.                 </td>  
  41.                 <td>  
  42.                     <asp:TextBox ID="txtPassword" runat="server" Width="175px" MaxLength="20" AutoCompleteType="Disabled"  
  43.                         TextMode="Password"></asp:TextBox>  
  44.                     <asp:RequiredFieldValidator ID="req_txtPassword" runat="server" ErrorMessage="Enter Password."  
  45.                         ToolTip="Enter Password." ControlToValidate="txtPassword" Text="*" Display="Static"  
  46.                         ForeColor="Red" ValidationGroup="vldLogin"></asp:RequiredFieldValidator>  
  47.                     <asp:FilteredTextBoxExtender ID="fltr_txtPassword" runat="server" FilterType="Numbers, LowercaseLetters, UppercaseLetters, Custom"  
  48.                         TargetControlID="txtPassword" ValidChars="@!_%$#">  
  49.                     </asp:FilteredTextBoxExtender>  
  50.                 </td>  
  51.             </tr>  
  52.             <tr>  
  53.                 <td>  
  54.                 </td>  
  55.                 <td colspan="2" align="left">  
  56.                     <div>  
  57.                         <asp:UpdatePanel ID="UpdatePanel1" runat="server">  
  58.                             <ContentTemplate>  
  59.                                 <asp:Image ImageUrl="ghCaptcha.ashx" runat="server" ID="imgCaptcha" />  
  60.                                 <asp:ImageButton ID="btnRefresh" runat="server" Width="10px" Height="10px" ImageUrl="~/refresh.jpg"  
  61.                                     OnClick="btnRefresh_Click" />  
  62.                             </ContentTemplate>  
  63.                         </asp:UpdatePanel>  
  64.                     </div>  
  65.                 </td>  
  66.             </tr>  
  67.             <tr>  
  68.                 <td align="right">  
  69.                     Enter Code :  
  70.                 </td>  
  71.                 <td>  
  72.                     <asp:TextBox ID="txtCode" runat="server" Width="175px" MaxLength="5" AutoCompleteType="Disabled"></asp:TextBox>  
  73.                     <asp:RequiredFieldValidator ID="req_txtCode" runat="server" ErrorMessage="Enter captcha code."  
  74.                         ToolTip="Enter captcha code." ControlToValidate="txtCode" Text="*" Display="Static"  
  75.                         ForeColor="Red" ValidationGroup="vldLogin"></asp:RequiredFieldValidator>  
  76.                     <asp:FilteredTextBoxExtender ID="fltr_txtCode" runat="server" FilterType="Numbers"  
  77.                         TargetControlID="txtCode">  
  78.                     </asp:FilteredTextBoxExtender>  
  79.                 </td>  
  80.             </tr>  
  81.             <tr>  
  82.                 <td colspan="2" align="center">  
  83.                     <asp:Button ID="btnLogin" runat="server" Text="Login" ValidationGroup="vldLogin"  
  84.                         OnClick="btnLogin_Click" />  
  85.                     <asp:ValidationSummary ID="ValidationSummary1" runat="server" ValidationGroup="vldLogin"  
  86.                         ShowSummary="false" ShowMessageBox="true" HeaderText="You have received following errors." />  
  87.                 </td>  
  88.             </tr>  
  89.         </tbody>  
  90.     </table>  
  91.     <table id="tblAlert" runat="server" width="40%" border="0" cellpadding="0" cellspacing="4"  
  92.         style="background-color: #cecece;" align="center" visible="false">  
  93.         <tr>  
  94.             <td align="center">  
  95.                 This user is already logged in. Do you want to terminate other active session.  
  96.             </td>  
  97.         </tr>  
  98.         <tr>  
  99.             <td align="center">  
  100.                 <asp:Button ID="btnOk" runat="server" Text="OK" OnClick="btnOk_Click" />  
  101.                 <asp:Button ID="btnCancel" runat="server" Text="Cancel" OnClick="btnCancel_Click" />  
  102.             </td>  
  103.         </tr>  
  104.     </table>  
  105.     </form>  
  106. </body>  
  107. </html>  

Login.aspx.cs Code behind

  1. #region " [ using ] "  
  2. using System;  
  3. using System.Configuration;  
  4. using System.Data;  
  5. using System.Data.SqlClient;  
  6. using System.Web.UI;  
  7. #endregion  
  8.   
  9. public partial class Login : System.Web.UI.Page  
  10. {  
  11.     protected void Page_Load(object sender, EventArgs e)  
  12.     {  
  13.         if (!Page.IsPostBack)  
  14.         {  
  15.             UpdateCaptchaText();  
  16.         }  
  17.     }  
  18.  
  19.     #region " [ Button Event ] "  
  20.     protected void btnRefresh_Click(object sender, ImageClickEventArgs e)  
  21.     {  
  22.         UpdateCaptchaText();  
  23.     }  
  24.   
  25.   
  26.     protected void btnLogin_Click(object sender, EventArgs e)  
  27.     {  
  28.         if (!string.Equals(txtCode.Text.Trim(), (string)Session["Captcha"]))  
  29.         {  
  30.             lblError.Text = "Enter correct code.";  
  31.             return;  
  32.         }  
  33.   
  34.         lblError.Text = string.Empty;  
  35.         DataTable dtUser = new DataTable();  
  36.         string userSession = Guid.NewGuid().ToString();  
  37.         Session["UserSession"] = userSession;  
  38.         try  
  39.         {  
  40.             dtUser = checkUserLogin(userSession, "LOGIN");  
  41.   
  42.             if (dtUser != null)  
  43.             {  
  44.                 if (dtUser.Columns.Contains("RES"))  
  45.                 {  
  46.                     lblError.Text = dtUser.Rows[0][0].ToString();  
  47.                     ClearPage();  
  48.                 }  
  49.                 else  
  50.                 {  
  51.                     Session["UserID"] = dtUser.Rows[0]["UserID"];  
  52.                     Session["UserName"] = dtUser.Rows[0]["UserName"];  
  53.                     Session["LastLogin"] = dtUser.Rows[0]["LastLogin"];  
  54.   
  55.                     if (string.Equals(dtUser.Rows[0]["IsLogin"].ToString(), "True"))  
  56.                     {  
  57.                         tblAlert.Visible = true;  
  58.                         tblLogin.Visible = false;  
  59.                     }  
  60.                     else  
  61.                     {  
  62.                         Response.Redirect("~/Welcome.aspx");  
  63.                     }  
  64.                 }  
  65.             }  
  66.             else  
  67.             {  
  68.                 ClearPage();  
  69.                 lblError.Text = "Unexpected error.";  
  70.             }  
  71.         }  
  72.         catch  
  73.         {  
  74.             throw;  
  75.         }  
  76.         finally  
  77.         {  
  78.             dtUser.Dispose();  
  79.         }  
  80.     }  
  81.   
  82.     protected void btnOk_Click(object sender, EventArgs e)  
  83.     {  
  84.         checkUserLogin(Session["UserSession"].ToString(), "CHANGELOGIN");  
  85.         Response.Redirect("~/Welcome.aspx");  
  86.     }  
  87.   
  88.     protected void btnCancel_Click(object sender, EventArgs e)  
  89.     {  
  90.         Response.Redirect("~/login.aspx");  
  91.     }  
  92.     #endregion  
  93.  
  94.     #region " [ Private Function ] "  
  95.     private DataTable checkUserLogin(string userSession, string mode)  
  96.     {  
  97.         DataSet dsData = new DataSet();  
  98.         SqlConnection sqlCon = null;  
  99.         SqlDataAdapter sqlCmd = null;  
  100.   
  101.         try  
  102.         {  
  103.             using (sqlCon = new SqlConnection(ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString))  
  104.             {  
  105.                 sqlCmd = new SqlDataAdapter("USP_UserLogin", sqlCon);  
  106.                 sqlCmd.SelectCommand.CommandType = CommandType.StoredProcedure;  
  107.                 sqlCmd.SelectCommand.Parameters.AddWithValue("@loginID", txtLogin.Text.Trim());  
  108.                 sqlCmd.SelectCommand.Parameters.AddWithValue("@password", txtPassword.Text.Trim());  
  109.                 sqlCmd.SelectCommand.Parameters.AddWithValue("@sessionID", userSession);  
  110.                 sqlCmd.SelectCommand.Parameters.AddWithValue("@mode", mode);  
  111.   
  112.                 sqlCon.Open();  
  113.                 sqlCmd.Fill(dsData);  
  114.   
  115.                 sqlCon.Close();  
  116.             }  
  117.         }  
  118.         catch  
  119.         {  
  120.             throw;  
  121.         }  
  122.         return dsData.Tables[0];  
  123.     }  
  124.   
  125.     private void ClearPage()  
  126.     {  
  127.         txtCode.Text = string.Empty;  
  128.         txtPassword.Text = string.Empty;  
  129.         txtCode.Text = string.Empty;  
  130.         UpdateCaptchaText();  
  131.     }  
  132.   
  133.     private void UpdateCaptchaText()  
  134.     {  
  135.         txtCode.Text = string.Empty;  
  136.         Random randNum = new Random();  
  137.   
  138.         //Store the captcha text in session to validate  
  139.         Session["Captcha"] = randNum.Next(10000, 99999).ToString();  
  140.         imgCaptcha.ImageUrl = "~/ghCaptcha.ashx?" + Session["Captcha"];  
  141.     }  
  142.     #endregion  

Expected result

1. If the user has logged into the application and then if the same credentials are used for the login on another machine then the user will receive an alert that another session is active, do you want to terminate it.



2. If the OK button is clicked then a new session is updated in usermaster table otherwise the page is refreshed.

Also on the home.aspx page it is required to check that the sessionID stored in the database and the sessionID received from the current user are the same. This ensures that only one session is active per user.

The following is the Page Load event of the Home.aspx page.

  1. protected void Page_Load(object sender, EventArgs e)  
  2.     {  
  3.         if (!Page.IsPostBack)  
  4.         {  
  5.             BindUserData();  
  6.             if (!CheckUserSession())  
  7.             {  
  8.                 Response.Redirect("~/login.aspx");  
  9.             }  
  10.         }  
  11.     }