ASP.NET Forms Authentication Best Practices
by Douglas Reilly


Listing One
(a)
<authentication mode="Forms" > 
    <forms 
       loginUrl="login.aspx"
       protection="All"
       timeout="30"
       path="/" />
</authentication>

(b)
<authorization>
   <deny users="?" /> 
</authorization>

(c)
<location path="Register.aspx">
  <system.web>
    <authorization>
      <allow users="?"/>
    </authorization>
  </system.web>
</location>

Listing Two 

using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Web.Security;

namespace FormsAuth
{
    /// <summary>
    /// Summary description for UserDB.
    /// </summary>
    public class UserDB
    {
        public static DataSet SelectUserInfo(string UserName)
        {
            string strCn;
            DataSet ds=null;
            if ( UserName==string.Empty || UserName==null )
            {
             throw new NullReferenceException("User Name Must Be Specified!");
            }
            strCn=System.Configuration.ConfigurationSettings.
                                               AppSettings["DSN"].ToString();
            SqlConnection cn=new SqlConnection(strCn);
            cn.Open();
            try
            {
                SqlCommand cmd=new SqlCommand("spSelectUserInfo",cn);
                cmd.CommandType=CommandType.StoredProcedure;
                cmd.Parameters.Add("@UserName",UserName);
                SqlDataAdapter da=new SqlDataAdapter(cmd);
                ds=new DataSet();
                da.Fill(ds,"User");
            }
            catch ( Exception )
            {
                // Do something...
            }
            finally
            {
                cn.Close();
            }
            return ds;
        }
        public static bool ChangePassword(int UserID, string NewPasswordHash,
                                         string Salt,bool MustChangePassword)
        {
            bool ret=false;
            if ( NewPasswordHash==string.Empty || UserID==0 )
            {
              throw new Exception("Not all required variables set in UserDB");
            }
            string strCn;
            strCn=System.Configuration.ConfigurationSettings.
                                               AppSettings["DSN"].ToString();
            SqlConnection cn=new SqlConnection(strCn);
            cn.Open();
            try
            {
                SqlCommand cmd=new SqlCommand("spSaveChangedPassword",cn);
                cmd.CommandType=CommandType.StoredProcedure;
                cmd.Parameters.Add("@UserID",UserID);
                cmd.Parameters.Add("@PasswordHash",NewPasswordHash);
                cmd.Parameters.Add("@Salt",Salt);
                cmd.Parameters.Add("@MustChangePassword",MustChangePassword);
                SqlParameter prm=new SqlParameter();
                prm.Direction=ParameterDirection.ReturnValue;
                prm.ParameterName="ReturnValue";
                cmd.Parameters.Add(prm);
                cmd.ExecuteNonQuery();
                if ( (int)cmd.Parameters["ReturnValue"].Value!=0 )
                {
                    ret=true;
                }
            }
            finally
            {
                cn.Close();
            }
            return ret;
        }
        public static bool SaveNewUser(string UserName, string LastName,
            string FirstName,string email,string PasswordHash,string Salt,
            bool MustChangePassword)
        {
            bool ret=false;
            string strCn;
            strCn=System.Configuration.ConfigurationSettings.
                                            AppSettings["DSN"].ToString();
            SqlConnection cn=new SqlConnection(strCn);
            cn.Open();
            try
            {
                SqlCommand cmd=new SqlCommand("spSaveNewUser",cn);
                cmd.CommandType=CommandType.StoredProcedure;
                cmd.Parameters.Add("@UserID",0);
                cmd.Parameters.Add("@UserName",UserName);
                cmd.Parameters.Add("@LastName",LastName);
                cmd.Parameters.Add("@FirstName",FirstName);
                cmd.Parameters.Add("@email",email);
                cmd.Parameters.Add("@PasswordHash",PasswordHash);
                cmd.Parameters.Add("@Salt",Salt);
                cmd.Parameters.Add("@MustChangePassword",MustChangePassword);
                SqlParameter prm=new SqlParameter();
                prm.Direction=ParameterDirection.ReturnValue;
                prm.ParameterName="ReturnValue";
                cmd.Parameters.Add(prm);
                cmd.ExecuteNonQuery();
                if ( (int)cmd.Parameters["ReturnValue"].Value!=0 )
                {
                    ret=true;
                }
            }
            finally
            {
                cn.Close();
            }
            return ret;
        }
    }
}


Listing Three

using System;
using System.Data;
using System.Security;
using System.Security.Cryptography;
using System.Web.Security;

namespace FormsAuth
{
    /// <summary>
    /// Summary description for User.
    /// </summary>
    public class User
    {
        private string m_LastName;
        private string m_FirstName;
        private string m_UserName;
        private string m_Email;
        private string m_Password;
        private bool m_MustChangePassword;
        private int m_UserID;

        #region Properties
        public string LastName
        {
            get { return m_LastName; }
            set { m_LastName=value; }
        }
        public string FirstName
        {
            get { return m_FirstName; }
            set { m_FirstName=value; }
        }
        public string UserName
        {
            get { return m_UserName; }
            set { m_UserName=value; }
        }
        public string Email
        {
            get { return m_Email; }
            set { m_Email=value; }
        }
        public string Password
        {
            get { return m_Password; }
            set { m_Password=value.ToLower(); }
        }
        public bool MustChangePassword
        {
            get { return m_MustChangePassword; }
            set { m_MustChangePassword=value; }
        }
        public int UserID
        {
            get { return m_UserID; }
            set { m_UserID=value; }
        }
        #endregion
        #region Private Methods
        private string CreateSalt(int size)
        {
            RNGCryptoServiceProvider rng=new RNGCryptoServiceProvider();
            byte[] buff=new byte[size];
            rng.GetBytes(buff);
            return Convert.ToBase64String(buff);
        }
        private string CreatePasswordHash(string pwd,string salt)
       {
            string saltAndPassword=string.Concat(pwd,salt);
            string hashedPassword= 
                FormsAuthentication.HashPasswordForStoringInConfigFile(
                saltAndPassword,"SHA1");
            return hashedPassword;
        }
        #endregion
        public User()
        {
            m_LastName=string.Empty;
            m_FirstName=string.Empty;
            m_UserName=string.Empty;
            m_Email=string.Empty;
            m_Password=string.Empty;
            m_UserID=0;
        }
        public bool VerifyPassword()
        {
            string PasswordHashFromDB;
            string strSalt;
            bool ret=false;
            if ( m_UserName==string.Empty || m_Password==string.Empty )
            {
                throw new NullReferenceException("Not all required 
                                                        properties set!");
            }
            try
            {
                DataSet ds=UserDB.SelectUserInfo(m_UserName);
                if ( ds.Tables.Count!=0 )
                {
                    strSalt=ds.Tables[0].Rows[0]["Salt"].ToString();
                    string hashedPasswordAndSalt = 
                        this.CreatePasswordHash(m_Password,strSalt);
                    PasswordHashFromDB=
                             ds.Tables[0].Rows[0]["PasswordHash"].ToString(); 
                    if ( PasswordHashFromDB!=string.Empty && 
                        PasswordHashFromDB.Equals(hashedPasswordAndSalt) )
                    {
                        m_UserID=int.Parse(ds.Tables[0].
                                             Rows[0]["PersonID"].ToString());
                        m_FirstName=ds.Tables[0].
                                             Rows[0]["FirstName"].ToString();
                        m_LastName=ds.Tables[0].
                                             Rows[0]["LastName"].ToString();
                        m_MustChangePassword=bool.Parse(ds.Tables[0].
                                    Rows[0]["MustChangePassword"].ToString());
                        m_Email=ds.Tables[0].Rows[0]["Email"].ToString();
                        ret=true;
                    }
                }
            }
            catch ( Exception exc )
            {
                // rethrow, or you could do something useful...
                throw exc;
           }
            finally
            {
            }
            return ret;
        }
        public bool ChangePassword(string NewPassword)
        {
            return ChangePassword(NewPassword,false);
        }
        public bool ChangePassword(string NewPassword,bool MustChangePassword)
        {
            bool ret=false;
            if ( this.UserID==0 )
            {
                throw new Exception("User Not Initialized. 
                                                    Can't change password.");
            }
            if ( NewPassword==string.Empty )
            {
                throw new NullReferenceException("Not all required arguments set!");
            }
            try
            {
                string salt=CreateSalt(5);
                string PasswordHash=CreatePasswordHash(NewPassword,salt);
                UserDB.ChangePassword(this.m_UserID,NewPassword,salt,
                                                      MustChangePassword);
                ret=true;
            }
            catch ( Exception )
            {
            }
            return ret;
       }
       public bool SaveNewUser(string UserName,string LastName,
       string FirstName,string email,string Password,bool MustChangePassword)
       {
            bool ret=false;
            string salt=CreateSalt(5);
            string PasswordHash=CreatePasswordHash(Password,salt);
            return UserDB.SaveNewUser(UserName,LastName,FirstName,
                           email,PasswordHash,salt,MustChangePassword);
        }

    }
}

Listing Four 
(a)
private string CreateSalt(int size)
{
   RNGCryptoServiceProvider rng=new RNGCryptoServiceProvider();
   byte[] buff=new byte[size];
   rng.GetBytes(buff);
   return Convert.ToBase64String(buff);
}

(b)
private string CreatePasswordHash(string pwd,string salt)
{
 string saltAndPassword=string.Concat(pwd,salt);
 string hashedPassword=FormsAuthentication.HashPasswordForStoringInConfigFile(
     saltAndPassword,"SHA1");
 return hashedPassword;
}

Listing Five
private void btnLogin_Click(object sender, System.EventArgs e)
{
    FormsAuth.User u=new FormsAuth.User();
    u.UserName=this.edUserName.Text;
    u.Password=this.edPassword.Text;
    if ( u.VerifyPassword()==true )
    {
        // Redirect, don't bother with persistant cookie.
        FormsAuthentication.RedirectFromLoginPage(u.UserName,false);
    }
    else
    {
        this.lblError.Text="Sorry - Could not log you in...";
    }
}
Listing Six

DataSet ds=UserDB.SelectUserInfo(m_UserName);
if ( ds.Tables.Count!=0 )
{
   strSalt=ds.Tables[0].Rows[0]["Salt"].ToString();
   string hashedPasswordAndSalt = this.CreatePasswordHash(m_Password,strSalt);
   PasswordHashFromDB=ds.Tables[0].Rows[0]["PasswordHash"].ToString(); 
   if ( PasswordHashFromDB!=string.Empty && 
        PasswordHashFromDB.Equals(hashedPasswordAndSalt) )
   {
        m_UserID=int.Parse(ds.Tables[0].Rows[0]["PersonID"].ToString());
        m_FirstName=ds.Tables[0].Rows[0]["FirstName"].ToString();
        m_LastName=ds.Tables[0].Rows[0]["LastName"].ToString();
        m_MustChangePassword=bool.Parse(
                   ds.Tables[0].Rows[0]["MustChangePassword"].ToString());
        m_Email=ds.Tables[0].Rows[0]["Email"].ToString();
        ret=true;
   }
}

Listing Seven
     
private void Button1_Click(object sender, System.EventArgs e)
{
    if ( Page.IsValid )
    {
        FormsAuth.User u=new FormsAuth.User();
        u.UserName=User.Identity.Name;
        u.Password=this.edOldPassword.Text;
        if ( u.VerifyPassword() )
        {
            if ( u.ChangePassword(edPassword1.Text) )
            {
                lblMessage.Text="Password Changed!";
            }
            else
            {
                lblMessage.Text="Password NOT Changed!";
            }
        }
    }
}

Listing Eight
private void Button1_Click(object sender, System.EventArgs e)
{
    if ( Page.IsValid )
    {
        FormsAuth.User u=new FormsAuth.User();
        if ( u.SaveNewUser(edUserName.Text,edLastName.Text,
            edFirstName.Text,edEmail.Text,edPassword1.Text,true) )
        {
            Response.Redirect("Login.aspx");
        }
        else
        {
            lblMessage.Text="Can't register that name. Please try again.";
        }
    }
}






7


