Using ASP.NET to Make a Secure Site and Move Login Processing Out of the Page

by Matt Rutledge


A lot of us are tasked everyday to come up with ways of making our site, and other sites secure. Well if you’re anything like me you don’t want to have to rewrite login code 20 times a day. I’m going to show you how to make a secure site and have the login processing in a separate class. Let’s get started.

In .NET to make a site secure is very simple, open up Web.Config file and look for the line that says <authentication mode="Windows" />. Delete that line and put in the following:


<authentication mode="Forms">
  <forms name="yourAuthCookie" loginUrl="login.aspx"
    protection="All" path="/" />

</authentication>
<authorization>
  <deny users="?" />
</authorization>

This now changes the way your web application will authenticate, it will no longer use NT security. Anybody trying to hit a page without authenticating first will be bounced back to the loginUrl that is specified.

Now I know a lot of you are thinking, but I have pages that aren’t secure that I want the user to see. Well that is easy. After the <system.web> tag in your Web.Config file has ended put the following:


  <location path="test.aspx">

    <system.web>
      <authorization>
        <allow users="?" />
      </authorization>
    </system.web>

  </location>

This will allow any user, authenticated or not, to access that page only, if you have a whole directory you want anyone to access just put the directory name in the path, and I mean just the name no slashes or wildcards.

Now that we have a secure site we need to make the login page that we specified in the loginUrl.

Just a quick note, I am going to be using C# in this example with a SQL database and I will supply a code sample.

Start off by creating a webform and put your form fields in there. Use web controls for your fields not HTML controls.


<%@ Page language="c#" Codebehind="login.aspx.cs"
  AutoEventWireup="false" Inherits="secure.login" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
  <HEAD>
    <title>Secure Site</title>
    <meta content="Microsoft Visual Studio 7.0" name="GENERATOR">

    <meta content="C#" name="CODE_LANGUAGE">
    <meta content="JavaScript" name="vs_defaultClientScript">
    <meta content="http://schemas.microsoft.com/intellisense/ie5"

      name="vs_targetSchema">
  </HEAD>
  <body MS_POSITIONING="GridLayout">
    <form id="login" method="post" runat="server">

      <table cellSpacing="0" cellPadding="0" border="0">
      <tr>
        <td vAlign="top" align="left">

          <asp:label id="Message" Runat="server" ForeColor="#ff0000">
          </asp:label>
        </td>
      </tr>

      <tr>
        <td vAlign="top" align="left">
          <b>E-mail:</b>
        </td>

      </tr>
      <tr>
        <td vAlign="top" align="left">
          <asp:textbox id="username" Runat="server" Width="120">

          </asp:textbox>
        </td>
      </tr>
      <tr>
        <td vAlign="top" align="left">

          <b>Password:</b>
        </td>
      </tr>
      <tr>
        <td vAlign="top" align="left">

          <asp:textbox id="password" Runat="server"
            Width="120" TextMode="Password">
          </asp:textbox>

        </td>
      </tr>
      <tr>
        <td vAlign="top" align="left">
          <asp:checkbox id="saveLogin" Runat="server"

            Text="<b>Save my login</b>">
          </asp:checkbox>
        </td>
      </tr>
      <tr>

        <td vAlign="top" align="right">
          <asp:imagebutton id="btnLogin" Runat="server"
            ImageUrl="/images/w2k/login/btnLogin.gif">

          </asp:imagebutton>
        </td>
      </tr>
      </table>
    </form>
  </body>

</HTML>

Now in the code behind page create a click event for the login button.


private void InitializeComponent()
{
  this.btnLogin.Click += new System.Web.UI.ImageClickEventHandler(this.btnLogin_Click);
  .
  .
  .
}

and put the function in.


    private void btnLogin_Click(object sender, System.Web.UI.ImageClickEventArgs e)
    {
      CCommonDB sql = new CCommonDB();
      string redirect = "";

      if((redirect = sql.AuthenticateUser(this.Session, this.Response,
        username.Text, password.Text, saveLogin.Checked)) != string.Empty)
      {
        // Redirect the user
        Response.Redirect(redirect);
      }
      else
      {
        Message.Text = "Login Failed!";
      }
    }

In this example I am actually returning a specified redirect, but if you would like to redirect them to the page that they came from you can always use:
FormsAuthentication.RedirectFromLoginPage(…)

Here is the CCommonDB class that I use to authenticate users.

CCommonDB.cs

namespace secure.Components
{
  public class CCommonDB : CSql
  {
    public CCommonDB() : base() { }

    public string AuthenticateUser(
      System.Web.SessionState.HttpSessionState objSession, // Session Variable
      System.Web.HttpResponse objResponse,                 // Response Variable
      string email,                                        // Login
      string password,                                     // Password
      bool bPersist                                        // Persist login
      )
    {
      int nLoginID  = 0;
      int nLoginType  = 0;

      // Log the user in
      Login(email, password, ref nLoginID, ref nLoginType);

      if(nLoginID != 0)  // Success
      {
        // Log the user in
        System.Web.Security.FormsAuthentication.SetAuthCookie(nLoginID.ToString(), bPersist);

        // Set the session varaibles
        objSession["loginID"]  = nLoginID.ToString();
        objSession["loginType"] = nLoginType.ToString();

        // Set cookie information incase they made it persistant
        System.Web.HttpCookie wrapperCookie = new System.Web.HttpCookie("wrapper");
        wrapperCookie.Value = objSession["wrapper"].ToString();
        wrapperCookie.Expires = DateTime.Now.AddDays(30);

        System.Web.HttpCookie lgnTypeCookie = new System.Web.HttpCookie("loginType");
        lgnTypeCookie.Value = objSession["loginType"].ToString();
        lgnTypeCookie.Expires = DateTime.Now.AddDays(30);

        // Add the cookie to the response
        objResponse.Cookies.Add(wrapperCookie);
        objResponse.Cookies.Add(lgnTypeCookie);

        return "/candidate/default.aspx";
          }
          case 1:  // Admin Login
          {
            return "/admin/default.aspx";
          }
          case 2:  // Reporting Login
          {
            return "/reports/default.aspx";
          }
          default:
          {
            return string.Empty;
          }
        }
      }
      else
      {
        return string.Empty;
      }
    }

    /// <summary>

    /// Verifies the login and password that were given
    /// </summary>
    /// <param name="email">the login</param>
    /// <param name="password">the password</param>

    /// <param name="nLoginID">returns the login id</param>
    /// <param name="nLoginType">returns the login type</param>
    public void Login(string email, string password, ref int nLoginID, ref int nLoginType)
    {
      ResetSql();

      DataSet ds = new DataSet();

      // Set our parameters
      SqlParameter paramLogin = new SqlParameter("@username", SqlDbType.VarChar, 100);
      paramLogin.Value = email;

      SqlParameter paramPassword = new SqlParameter("@password", SqlDbType.VarChar, 20);
      paramPassword.Value = password;


      Command.CommandType = CommandType.StoredProcedure;
      Command.CommandText = "glbl_Login";
      Command.Parameters.Add(paramLogin);
      Command.Parameters.Add(paramPassword);

      Adapter.TableMappings.Add("Table", "Login");
      Adapter.SelectCommand = Command;
      Adapter.Fill(ds);

      if(ds.Tables.Count != 0)
      {
        DataRow row = ds.Tables[0].Rows[0];

        // Get the login id and the login type
        nLoginID  = Convert.ToInt32(row["Login_ID"].ToString());
        nLoginType  = Convert.ToInt32(row["Login_Type"].ToString());
      }
      else
      {
        nLoginID = 0;
        nLoginType = 0;
      }
    }
  }

  abstract public class CSql
  {
    private SqlConnection sqlConnection;      // Connection string
    private SqlCommand sqlCommand;          // Command
    private SqlDataAdapter sqlDataAdapter;      // Data Adapter
    private DataSet sqlDataSet;            // Data Set

    public CSql()
    {
      sqlConnection  = new SqlConnection(ConfigurationSettings.AppSettings["ConnectionString"]);
      sqlCommand    = new SqlCommand();
      sqlDataAdapter  = new SqlDataAdapter();
      sqlDataSet    = new DataSet();

      sqlCommand.Connection = sqlConnection;
    }

    /// <summary>

    /// Access to our sql command
    /// </summary>
    protected SqlCommand Command
    {
      get { return sqlCommand; }
    }

    /// <summary>
    /// Access to our data adapter
    /// </summary>
    protected SqlDataAdapter Adapter
    {
      get { return sqlDataAdapter; }
    }

    /// <summary>
    /// Makes sure that everything is clear and ready for a new query
    /// </summary>

    protected void ResetSql()
    {
      if(sqlCommand != null)
      {
        sqlCommand = new SqlCommand();
        sqlCommand.Connection = sqlConnection;
      }
      if(sqlDataAdapter != null)
        sqlDataAdapter = new SqlDataAdapter();

      if(sqlDataSet != null)
        sqlDataSet = new DataSet();
    }

    /// <summary>
    /// Runs our command and returns the dataset
    /// </summary>
    /// <returns>the data set</returns>
    protected DataSet RunQuery()
    {
      sqlDataAdapter.SelectCommand = Command;

      sqlConnection.Open();
      sqlConnection.Close();

      sqlDataAdapter.Fill(sqlDataSet);

      return sqlDataSet;
    }
  }
}

I have two classes here, a base class for the common sql functions that most of use normally do and then a class that inherits from that class to separate the code.

When you look at the code you may ask why do you pass the session variable and response variable into the function, well if you have ever tried to access the Session or Response variable from a separate cs class you always get an error saying that you need reference to the object, and in this case you can just pass the objects in and out easily and quickly without having to try to create your own instance of the Session or Response.

Matt Rutledge

www.datausa.com

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read