User Login with Salted and Hashed passwords - Security Series #4.5

Ok, so we hashed our password in our last entry, now what do we do with it? We can't "unhash" it. So how can we tell if the user gives us the correct password when they try to log in.

Well, to demonstrate this we are going to need a simple login form and a simple user service. I am using the same userService.cfc as I have in previous examples. It can be instantiated in your onApplicationStart() like so


<cfset application.userService = CreateObject('component', 'cfc.userService').init("myDSN") />

We will get to what goes inside of userService.cfc shortly. First we need a simple login form.


<!--- If there is an error message, display it --->
<cfif IsDefined("errorMessage") AND Len(errorMessage)>
    <cfoutput>#errorMessage#</cfoutput>
</cfif>

<!--- Login form --->
<cfform name="loginForm" action="#cgi.script_name#" method="post">
    Username : <cfinput type="text" name="username" value="" /><br />
    Password : <cfinput type="password" name="password" value="" /><br />
    <cfinput type="submit" name="btnSubmit" value="Login" />
</cfform>

Next, above our simple login form, we need some code for handling our posted form.


<!--- If the form is not empty, it has been posted --->
<cfif IsDefined("form") and NOT StructIsEmpty(form)>
    <cfif IsDefined("form.username") AND IsDefined("form.password")>
        <!--- If all form fields are present, send to validation function --->
        <cfset userValid = application.userService.validateUser(form.username, form.password)>
    <cfelse>
        <!--- If form fields were missing, set user as invalid --->
        <cfset userValid = false />
    </cfif>
    
    <!--- Check to see if a valid user was returned --->    
    <cfif userValid>
        <!--- if user was valid, reroute them to their myAccount page --->
        <cfset session.isLoggedIn = true />
        <cflocation url="myaccount.cfm">
    <cfelse>
        <!--- If not valid, set error message --->
        <cfset errorMessage = "Invalid Username or Password" />
    </cfif>
</cfif>

So, as you can see, Our code is checking to see if the form was posted, if it was posted, it checks to see if the appropriate fields were included. This is important because hackers will try to intercept your forms and add and remove fields to see if they can get around your login.

If everything checks out, validateUser() is invoked from the userService and the username and password are passed in.

If the function returns false, or if any of the form fields are missing, userValid is set to false and an error message is set. If the function returns true, then the user is re-routed to the myAccount.cfm page after a session variable has been set for persistence.

So now, what is going on inside of userService.validateUser()?


<cffunction name="validateUser" access="public" output="false" returntype="boolean">
    <!--- Accept username and password as args --->
    <cfargument name="username" required="false" default="" type="string" />
    <cfargument name="password" required="false" default="" type="string" />
    
    <!--- Set local var for query --->
    <cfset var qGetUserInfo = "" />
    <cfset var inputHash = "" />
            
    <!--- Using the username, get the hashed password and the salt (If the username is invalid, then nothing will be returned) --->
    <cfquery name="qGetUserInfo" datasource="#variables.dsn#">
        SELECT u.password, us.salt
        FROM users u
            JOIN user_salts us ON u.userid = us.userid
        where u.username = <cfqueryparam value="#arguments.username#" cfsqltype="cf_sql_varchar" />
    </cfquery>
    
    <!--- if the query returns a record then the username was legit, lets look at the passwords --->
    <cfif qGetUserInfo.recordcount>
        <!--- Set the input hash by concatenating the password that was passed in to the salt from the DB and hashing it witht he same hash function as when it was stored. --->
        <cfset inputHash = Hash(arguments.password & qGetUserInfo.salt, 'SHA-512') />
        
        <!--- Compare the inputHash with the hash we pulled from the db if they match, then the correct password was passed in --->
        <cfreturn NOT Compare(inputHash, qGetUserInfo.password) />
    <cfelse>
        <!--- If no user info was returned then it is a bad username, return false --->
        <cfreturn false />
    </cfif>

</cffunction>

ValidateUser() is expecting two arguments: username and password. They are not required and will default to blank strings since they will fail our tests if they are blank (unless you have a user with a blank username and a blank password, which would be bad database design as neither should be allowed to be NULL).

Our local variables are set. We need one for the query and one for the inputHash. If you don't know why we are doing this, look at Adrian's post about the var scope.

Next we query the database for the previously hashed password and salt using the username in the where clause. If it returns a value, then the username was legit.

If the query returns a value, then we append the salt we pulled from the database onto our user input password. Finally, using the same hash function we used when we created the original hash, we create inputHash.

We then compare the new hash(inputHash) to the original hash(qGetUserInfo.password) that we pulled from the database. If they match, then the correct password was submitted and TRUE is returned to our calling page and the user is re-routed to the myAccount.cfm page.

This is obviously a very simple example of how you would implement a login. I have not done much here to persist the users validation. We will save that for our discussion of session management.

DISCLAIMER: The code in this post has not been tested. I threw it together late tonight and did not have a DB server set up to test my queries, so please take it for what it is, which is a rough and simple example. I may try to get an entire working example available for download if people express interest in the comments.

Comments
Chris's Gravatar Jason,

Great post. This series has been very helpful!

Chris
# Posted By Chris | 6/3/08 7:29 AM
Jason Dean's Gravatar Thanks Chris. I am enjoying doing it, so it works out well. Soon I am hoping to start the next sub-series on Session Management and Session Security. But that has a lot of research that goes along with it, so I may be doing to smaller topics in between.
# Posted By Jason Dean | 6/3/08 3:32 PM
Jared Rypka-Hauer's Gravatar Jason,

I notice that on the password handler you're not iterating the password & salt hashing 1025 times like you were in 4.4.

I'm guessing that this is just an oversight? It seems like you'd need to do the exact same operation on the password as you did when you saved it to the DB?

Actually, for that matter, in 4.4 you went to the trouble of creating a hashPassword(pwd) service method... why not call that here so you're doing COMPARE (hasPassword(inputPWD), qGetUserInfo.password)?

Maybe time to update some code again, hehe...
# Posted By Jared Rypka-Hauer | 6/29/10 12:47 PM
Jason Dean's Gravatar @Jared,

Yeah, you're right, I'm not. Definitely an oversight.

I really should update this. I will look at it when I get back from vacation. Thanks.
# Posted By Jason Dean | 6/29/10 9:55 PM
Chris's Gravatar @Jason - Thanks for the great series! To build off of Jared's comment, you could add an optional salt argument to your hashPassword method. If provided, hashPassword would use it instead of creating one via CreateUUID(). If not provided, then you'd obviously run the create uuid. This would allow for greater reusability of the hashPassword method IMO.
# Posted By Chris | 7/26/12 10:36 AM
Derek's Gravatar @Jason...
I guess you never got around to fixing the code? ;)
# Posted By Derek | 10/31/12 12:38 PM
Jason Dean's Gravatar Nope, Guess not.
# Posted By Jason Dean | 10/31/12 12:44 PM
Miles's Gravatar @jason - came across this from a stackoverflow link, and while it needs a few loop iterations, it's still a great post so many years later. =)
# Posted By Miles | 11/2/13 11:14 PM
jenny's Gravatar Now it is the time when you can also have the sims mobile hack generator at http://simsmobileguide.com/ so that there is enough free sims cash in your account.
# Posted By jenny | 3/18/18 3:33 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner