CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Eric Wise

Business & .NET

ASP .NET Forms Authentication Password Hashes- Level 200

A common question I get from ASP .NET developers is how to quickly and easily set up forms authentication where the passwords in the database are not stored in plain text.  It actually shocks me how many systems I've gone into on the database end and seen passwords for every account in the application sitting there in clear text.  Here's how Easy Assets .NET handles hashing passwords with a random salt value.

First let's take a look at our LoginUser object:

    1 Imports System.Web.Security
    2 Imports System.Security.Cryptography
    3  
    4 Public Class LoginUsers
    5     Inherits DomainObjects
    6  
    7     Public Overloads Sub Add(ByVal newItem As AcquisitionMethod)
    8         MyBase.List.Add(newItem)
    9     End Sub
   10  
   11     Default Public Shadows Property Item(ByVal index As Integer) As AcquisitionMethod
   12         Get
   13             Return CType(list(index), AcquisitionMethod)
   14         End Get
   15         Set(ByVal Value As AcquisitionMethod)
   16             list(index) = Value
   17         End Set
   18     End Property
   19 End Class
   20  
   21 Public Class LoginUser
   22     Inherits DomainObject
   23  
   24 #Region "Constructors"
   25     Public Sub New()
   26         Me.UserName = ""
   27     End Sub
   28  
   29     Public Sub New(ByVal LoginName As String)
   30         Me.UserName = LoginName
   31     End Sub
   32 #End Region
   33  
   34 #Region "Private Members"
   35     Private _username As String
   36     Private _emailaddress As String
   37     Private _firstname As String
   38     Private _issuesolver As Boolean
   39     Private _lastname As String
   40     Private _password As String
   41     Private _roles As System.Text.StringBuilder
   42     Private _salt As String
   43     Private _isActive As Boolean
   44 #End Region
   45  
   46 #Region "Properties"
   47     Public Property EmailAddress() As String
   48         Get
   49             Return Me._emailaddress
   50         End Get
   51         Set(ByVal value As String)
   52             Me._emailaddress = value
   53         End Set
   54     End Property
   55  
   56     Public ReadOnly Property FirstLast() As String
   57         Get
   58             Return (Me._firstname & " " & Me._lastname)
   59         End Get
   60     End Property
   61  
   62     Public Property FirstName() As String
   63         Get
   64             Return Me._firstname
   65         End Get
   66         Set(ByVal value As String)
   67             Me._firstname = value
   68         End Set
   69     End Property
   70  
   71     Public Property IssueSolver() As Boolean
   72         Get
   73             Return Me._issuesolver
   74         End Get
   75         Set(ByVal value As Boolean)
   76             Me._issuesolver = value
   77         End Set
   78     End Property
   79  
   80     Public ReadOnly Property LastFirst() As String
   81         Get
   82             If ((Not Me._lastname Is String.Empty) AndAlso (Not Me._firstname Is String.Empty)) Then
   83                 Return (Me._lastname & ", " & Me._firstname)
   84             End If
   85             Return ""
   86         End Get
   87     End Property
   88  
   89     Public Property LastName() As String
   90         Get
   91             Return Me._lastname
   92         End Get
   93         Set(ByVal value As String)
   94             Me._lastname = value
   95         End Set
   96     End Property
   97  
   98     Public Property Password() As String
   99         Get
  100             Return Me._password
  101         End Get
  102         Set(ByVal Value As String)
  103             Me._password = Value
  104         End Set
  105     End Property
  106  
  107     Public Property salt() As String
  108         Get
  109             Return Me._salt
  110         End Get
  111         Set(ByVal Value As String)
  112             Me._salt = Value
  113         End Set
  114     End Property
  115  
  116     Public ReadOnly Property Roles() As String
  117         Get
  118             If IsNothing(_roles) Then
  119                 Me._roles = New System.Text.StringBuilder
  120  
  121                 Dim query As New EasyAssets.DAC.UserRoleQuery("RoleDescription")
  122                 query.UserRole = New EasyAssets.DAC.UserRole
  123                 query.UserRole.UserName = Me._username
  124  
  125                 Dim dt As DataTable = ObjectDomainMGR.ListSummary(query)
  126                 For Each dr As DataRow In dt.Rows
  127                     Me._roles.Append(dr("RoleName") & "|")
  128                 Next
  129             End If
  130  
  131             Return Me._roles.ToString()
  132         End Get
  133     End Property
  134  
  135     Public Property UserName() As String
  136         Get
  137             Return Me._username
  138         End Get
  139         Set(ByVal value As String)
  140             Me._username = value
  141             Me.Key = value
  142         End Set
  143     End Property
  144  
  145     Public Property isActive() As Boolean
  146         Get
  147             Return _isActive
  148         End Get
  149         Set(ByVal Value As Boolean)
  150             _isActive = Value
  151         End Set
  152     End Property
  153 #End Region
  154  
  155 #Region "Methods"
  156     Private Sub CreatePasswordHash()
  157         Dim passwordSalt As String = (_password & _salt)
  158         _password = FormsAuthentication.HashPasswordForStoringInConfigFile(passwordSalt, "SHA1")
  159     End Sub
  160  
  161     Private Sub CreateSalt(ByVal size As Integer)
  162         Dim provider1 As New RNGCryptoServiceProvider
  163         Dim buffer1 As Byte() = New Byte(size - 1) {}
  164  
  165         provider1.GetBytes(buffer1)
  166         _salt = Convert.ToBase64String(buffer1)
  167     End Sub
  168  
  169     Public Sub SetPassword()
  170         CreateSalt(5)
  171         CreatePasswordHash()
  172     End Sub
  173 #End Region

Creating/Changing a Password

When you load the loginuser object from the database the password hash and the salt value are stored in the appropriate properties.  If you are creating a new user or wish to change the password of an existing user you simply set the Password property to the plain text value of the password from your form.  Then you call the SetPassword() method which generates a random salt value and uses it in conjunction with the plain text password to create the hash.

Logging In

So how do we authenticate the user then?  The code is fairly simple.  The Login.aspx page has a username and password textboxes and a button to click.  The button code grabs the LoginUser from the database specified by the username and if the user is active uses the salt value to generate a hash for comparison.  The code looks like this:

    Private Sub btnLogin_Click(ByVal sender As System.Object, ByVal e As System.Web.UI.ImageClickEventArgs) Handles btnLogin.Click
        Try
            Dim thisUser As New EasyAssets.DAC.LoginUser(txtUserName.Text)
 
            thisUser = DomainManager.Load(thisUser)
 
            If thisUser.isActive Then
                Dim passwordAndSalt As String = String.Concat(txtPassword.Text, thisUser.salt)
                Dim hashedPasswordAndSalt As String = FormsAuthentication.HashPasswordForStoringInConfigFile(passwordAndSalt, "SHA1")
 
                If Not thisUser.Password.Equals(hashedPasswordAndSalt) Then
                    Throw New System.Exception("Invalid Username/Password combination!")
                End If
 
                Dim authTicket As New FormsAuthenticationTicket(1, thisUser.UserName, DateTime.Now, DateTime.Now.AddMinutes(30), False, thisUser.Roles)
                Dim encryptedTicket As String = FormsAuthentication.Encrypt(authTicket)
 
                Dim authCookie = New HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
                Response.Cookies.Add(authCookie)
            Else
                Throw New Exception("Account Expired!  Contact an administrator.")
            End If
 
            Response.Redirect("Welcome.aspx", False)
        Catch ex As Exception
            WriteMessage(ex.Message, True)
        End Try
    End Sub

Role Based Security

You'll notice that the authentication ticket being created passes in the LoginUser's Roles.  In EasyAssets .NET there are a list of roles that any user can be assigned to and they grant or deny access to various parts of the application.  You'll notice that the Roles() property of the LoginUser queries the database and then builds a StringBuilder with the role names delimited by the pipe |.  The end result is that if you have a role called "ADMIN" you can simply do context.user.identity.isInRole("ADMIN") to validate whether that user is the admin.

How does ASP .NET handle building the ticket and setting the roles on the context.user?  Simply add the following code to your global.asax:

    Sub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As EventArgs)
        Dim CookieName As String = FormsAuthentication.FormsCookieName
        Dim authCookie As HttpCookie = Context.Request.Cookies(CookieName)
 
        'Check for cookie
        If IsNothing(authCookie) Then Return
 
        Dim authTicket As FormsAuthenticationTicket
        Try
            authTicket = FormsAuthentication.Decrypt(authCookie.Value)
 
        Catch
            Return
        End Try
 
        If IsNothing(authTicket) Then
            'Cookie failed to decrypt
            Return
        End If
 
        Dim roles() As String = authTicket.UserData.Split("|")
        Dim id As New FormsIdentity(authTicket)
        Dim principal As New System.Security.Principal.GenericPrincipal(id, roles)
        Context.User = principal
    End Sub

Finally, set your web.config to be forms authentication mode as follows:

<authentication mode="Forms">

   <forms name="EasyAssetFormsAuth" loginUrl="WebModules/Security/LogIn.aspx" protection="Encryption" timeout="30" path="/">

   forms>

authentication>

<authorization>

   <deny users="?" />

   <allow users="*" />

authorization>



Check out Devlicio.us!

Our Sponsors

Free Tech Publications