20 Replies Latest reply on Aug 29, 2016 8:29 AM by Steve Sommers

    Best practices for secure login of website/app

    WolfShade Level 4

      Hello, all,

       

      I am nearing the end of my side-project, and could use some advice on creating a secure login for it.

       

      First off, I would really like to avoid using CFLOGIN/CFLOGINUSER, if at all possible.  I know a lot of people use it, and all, but after some things that I've read online, I'm not comfortable using it.  I would like to create one from scratch.  I've Googled for it, but I'm not finding anything that DOESN'T use CFLOGIN.

       

      That said, another thing that I'm concerned about is keeping an admin logged in beyond what any session scope can do.  No matter how long I programmatically set the session for, I believe that it cannot surpass what is set in CFAdmin.  So if I manually set the session to expire in an hour, but the CFAdmin says 20 minutes, the session won't last beyond 20 minutes.  :/

       

      One more thing.    (I kind of feel like Columbo, now)  Concurrent logins.  I want to disallow them, and set it so that if an admin is logged in, already, that a second login without a logout, first, will deny the second attempt at logging on.  Is this difficult to arrange?

       

      I have included the class for BCrypt in my admin section and was planning on using that for encrypting the passwords before inserting them into the database.  Yea, or nay?

       

      V/r,

       

      ^_^

       

      UPDATE: ALSO - how difficult is it to set it so that I can see who is logged on, and "boot" anyone (assuming I think their account has been compromised)?

        • 1. Re: Best practices for secure login of website/app
          WolfShade Level 4

          Has anyone created a custom secure login for a website or webapp that did not use CFLOGIN?

           

          V/r,

           

          ^_^

          • 2. Re: Best practices for secure login of website/app
            Steve Sommers Level 4

            In no particular order...

             

            I've never used CFLogin and to tell you the truth, I'm not sure what it does any different than simply tracking the login status yourself in the session scope. I've read the doc a few times and it's unclear to me what it does.

             

            BCrypt, definitely or something similar. Make sure you are using a one-way hash function of some sort as opposed to a two-way encryption/decryption function. I like to use com.lambdaworks.crypto.SCryptUtil, but CF11+ comes with similar native functions either within CF or within the jars that come with CF.

             

            The newer CF admin page has default session timeframes and maximum timeframes. You cannot exceed the maximum idle setting from the application. But remember, this is an idle timeout so as long as the session is being used it will not timeout.

             

            Booting someone out, should be doable but a little work needs to be done. You need to track active logins within the application scope yourself. I have not done this but it sounds like a fun project and I would guess there might even be an article or two around on this topic.

             

            Lastly the single login, the thing to worry about is that more times than not the last attempt, or the attempt you would be blocking in your example, is the most accurate attempt so it would be better to kill the previous login sessions. Often people close browsers or shutoff their computer and then return to the site. The original login is no longer accessible to the user so blocking this new request may only frustrate users. Just something to think about and this may require some of the same logic as the "booting someone out" mentioned above.

            • 3. Re: Best practices for secure login of website/app
              WolfShade Level 4

              Hi, Steve Sommers,

               

              Thanks for your reply.

               

              I've never used CFLogin and to tell you the truth, I'm not sure what it  does any different than simply tracking the login status yourself in the  session scope. I've read the doc a few times and it's unclear to me  what it does.

              Ditto.  But I have read posts by respected CF "celebrities" that give CFLOGIN a less-than-desirable status in my mind, so in addition to not fully understanding how it works (despite repeated reading of documentation), I'm not inclined to use it because of that.

               

              So, how do _you_ track the login status?  I've never created a secure login; where I'm currently employed, the network handles authentication/authorization via Common Access Cards.

               

              But remember, this is an idle timeout so as long as the session is being used it will not timeout.

              Precisely.  The people who will be administering this project aren't the type to write a news item in Notepad, then copy/paste into the CMS; rather, they'd most likely bring up the page to enter an article, and type the whole thing out.  Great, if it takes less than 20 minutes!  But, you know.. formatting, proofreading, etc.  There's a good chance that they would timeout before hitting the submit button.  I _could_ write a JavaScript function that would keep the page alive, but that feels "hackish", to me.  Was hoping for a better alternative.

               

              Booting someone out, should be doable but a little work needs to be  done. You need to track active logins within the application scope  yourself. I have not done this but it sounds like a fun project and I  would guess there might even be an article or two around on this topic.

              Hmm.. sounds like I should shelf that for v1.1, then.    This project, even though I'm doing it for free, is way behind, already.  Maybe a future update.

               

              The original login is no longer accessible to the user so blocking this  new request may only frustrate users. Just something to think about and  this may require some of the same logic as the "booting someone out"  mentioned above.

              Point conceded.  Hadn't thought about people who just close their browsers without logging off.  I was more concerned with an authorized user being booted by someone who compromised their account, but you're right - clearing the first session to make the second session authorized is the best practice.

               

              V/r,

               

              ^_^

              • 4. Re: Best practices for secure login of website/app
                Steve Sommers Level 4

                WolfShade wrote:

                 

                So, how do _you_ track the login status? I've never created a secure login; where I'm currently employed, the network handles authentication/authorization via Common Access Cards.

                I create a login page, authenticate the user and if valid I create a user bean (I use FW/1 and it likes beans and after you get the hang of them they are flexible and powerful) and store this bean in the session scope. The bean includes any rights the user may have (admin, standard user, editor, etc.). Then somewhere in the request startup process I check the rights required for the requested page or action and validate against the user bean (or if the user bean even exists in the event of a user not logged in). To logout, simply delete the user bean from the session.

                 

                You may want to research a little more about how to apply rights and roles to a user and site. I based the framework that I use on an article for FuseBox and it described rights as locks and keys -- locks applied to the site and keys given to users. Once you have a basic framework the rest is easier than you may think at this time.

                1 person found this helpful
                • 5. Re: Best practices for secure login of website/app
                  Steve Sommers Level 4

                  In rereading your question, you definitely want to focus on creating a rights & roles or locks & keys framework. Then you get the roles either tracked in your application associated to the authenticated user or directly from your network authentication module (the latter is my suggestion).

                  • 6. Re: Best practices for secure login of website/app
                    WolfShade Level 4

                    Thanks for your input, Steve Sommers.  I haven't tried anything, yet, but this is being marked "Correct", anyway, because it does make sense.

                     

                    V/r,

                     

                    ^_^

                    • 7. Re: Best practices for secure login of website/app
                      WolfShade Level 4

                      Okay, so I've started this portion of the project, and I'm banging my head against a familiar brick wall that I don't remember how I fixed in a similar situation.

                       

                      The login form submits to itself.  If the credentials match, it sets session variables and continues on to the page that was attempted to load.

                       

                      In my onRequestStart(), I have code similar to:

                       

                      <cfparam name="session.loggedin.errMsg" default="" />
                      <!--- this is primarily to ensure that 
                            SOMETHING is in the session.loggedin scope --->
                      
                      <cfif NOT StructKeyExists(session.loggedin,'adminName') AND
                          (trim(cgi.script_name) neq "login.cfm" OR trim(cgi.script_name) neq "logout.cfm")>
                          <cflocation addtoken="no" url="/admin/login.cfm" />
                      </cfif>
                      

                       

                      But when the login form is submit and the credentials match, a CFDUMP shows that session.loggedin.adminName does exist and is populated, however I am still redirected back to the login form.  I can click a link and go to the chosen page, but the actual logging in doesn't work, even though the proper session variable exists.

                       

                      I even added sleep(1000) before the successful-login-redirect, and it still isn't working.

                       

                      What am I forgetting? 

                       

                      V/r,

                       

                      ^_^

                      • 8. Re: Best practices for secure login of website/app
                        EddieLotter Level 3

                        I believe you need to change your "OR" to "AND" otherwise that condition always evaluates to true.

                         

                        Cheers

                        Eddie

                        • 9. Re: Best practices for secure login of website/app
                          EddieLotter Level 3

                          Sorry, I'm wrong. The logic looks good.

                           

                          Cheers

                          Eddie

                          • 10. Re: Best practices for secure login of website/app
                            WolfShade Level 4

                            Nope, you were right.  I did wind up changing it to AND because with OR, it endlessly redirected. 

                             

                            Any thoughts on the whole "session variable is set but I still get redirected" thang?  I feel like the pirate who walks into a bar with the ships wheel on his belt.

                             

                            V/r,

                             

                            ^_^

                            • 11. Re: Best practices for secure login of website/app
                              WolfShade Level 4

                              Any suggestions, Steve Sommers?  I haven't been able to fix the session-exists-but-still-redirects-to-login issue.

                               

                              V/r,

                               

                              ^_^

                              • 12. Re: Best practices for secure login of website/app
                                Steve Sommers Level 4

                                I'm not seeing any of the validation and session setting logic so I would need more info. The only time I've had session problems is when cookies are not being tracked properly and the redirect actually creates a new session.

                                • 13. Re: Best practices for secure login of website/app
                                  WolfShade Level 4

                                  And I did see an article or forum post, somewhere, that mentioned exactly that.  I followed one of the four pieces of advice (using two CFHEADER tags) and it didn't work.

                                   

                                  My code (minus the two CFHEADER tags) is in post #7.  If you can see something that I'm missing, please let me know.

                                   

                                  Sorry, Steve Sommers.. Nevermind.. blame lack of sleep.. and lack of caffeine.. I just re-read what you typed, and it seems so much clearer, now. 

                                   

                                  I'll try to get something pseudo-code up, soon.  It's all at home and I don't have access to it from here.  But it's very basic.  I'm using BCrypt to hash the passwords for storage in the database, and the login form gets a user record just on username and "active" status, then compares the supplied password to the hashed password in the database.  If it doesn't match, kill the loggedin session and abort; if it does match, set session variables.  Once authenticated, the session variables _are_ set, but the redirect in Application.cfc doesn't see it in time.  I can still click a nav item (home, about, pics, vids, events), and will be redirected without any issue.  It's just that initial immediately-after-login that doesn't see the session variables.

                                   

                                  According to that blog that I read, this most likely is because after the form is submitted, a new CFID and CFTOKEN are issued.  (shrug)

                                   

                                  V/r,

                                   

                                  ^_^

                                  • 14. Re: Best practices for secure login of website/app
                                    WolfShade Level 4

                                    Sorry for taking so long to get this here.  I really don't have an easy way of copying/pasting code from my dev system to the internet. 

                                     

                                    Anyhoo.. the very first thing in my onRequestStart (after the "thePage" argument) is:

                                     

                                            <cfparam name="session.loggedin.errMsg" default="" />
                                            <cfparam name="session.loggedin.adminName" default="" />
                                            <cfif NOT len(session.loggedin.adminName) AND (trim(cgi.SCRIPT_NAME) neq "/z/z_login.cfm" AND trim(cgi.SCRIPT_NAME) neq "/z/z_logout.cfm")>
                                                <cflocation addtoken="no" url="/z/z_login.cfm" />
                                            </cfif>
                                    

                                     

                                    The z_login.cfm page:

                                     

                                    <cfsilent>
                                        <cfparam name="variables.redirect" default="/z/home.cfm" />
                                        <cfif len(trim(cgi.http_referer))><cfset variables.redirect = trim(cgi.http_referer) /></cfif>
                                        <cfparam name="session.sleepduration" default=2000 />
                                        <cfparam name="session.loggedin.errMsg" default="" />
                                        <cfif StructKeyExists(form,'redirect')>
                                            <cfset variables.chkAdmin = new components.ADMIN().chkAdmin(loginName=trim(form.loginName)) /><!--- Query to check if user is an admin and has rights --->
                                            <cfswitch expression="#variables.chkAdmin.recordCount#">
                                                <cfcase value="1">
                                                    <cfif request.bcrypt.checkpw(form.loginPW,variables.chkAdmin.adminPW)>
                                                        <cfset session.loggedin.adminName = variables.chkAdmin.adminName />
                                                        <cfset session.loggedin.adminRoles = variables.chkAdmin.adminRoles />
                                                        <cfset session.loggedin.lastLogin = DateTimeFormat(variables.chkAdmin.lastLogin,'hh:nntt MMM dd, YYYY') />
                                                        <cfset updateLogin = new components.ADMIN().llAdmin(adminID = variables.chkAdmin.adminID) />
                                                        <cfheader statuscode="302" statustext="Object Temporarily Moved" /><!--- This _SHOULD_ redirect to a page other than the login page; but it isn't --->
                                                        <cfheader name="location" value="#variables.redirect#" />
                                                    <cfelse>
                                                        <cfset tmp = StructDelete(session,'loggedin') />
                                                        <cfset session.loggedin.errMsg = "<h5>Login failed</h5>" />
                                                        <cfscript>sleep(session.sleepduration);</cfscript>
                                                        <cfset session.sleepduration = session.sleepduration + 1000 />
                                                    </cfif>
                                                </cfcase>
                                                <cfdefaultcase>
                                                    <cfset tmp = StructDelete(session,'loggedin') />
                                                    <cfset session.loggedin.errMsg = "<h5>Login failed</h5>" />
                                                    <cfscript>sleep(session.sleepduration);</cfscript>
                                                    <cfset session.sleepduration = session.sleepduration + 1000 />
                                                </cfdefaultcase>
                                            </cfswitch>
                                        </cfif>
                                        <cfset variables.thisTitle = "ADMINS" />
                                        <cfset variables.thisNav = "ADMINS" />
                                        <cfset variables.imgTxt = "CMS login" />
                                        <cfset tmp = StructDelete(session,'admins') />
                                    </cfsilent><cfif val(session.sleepduration) gt 10000>NO MORE ATTEMPTS<cfabort></cfif><cfinclude template="#request.webRoot#header.cfm" />
                                    <div id="main_area">
                                        <div>
                                            <div class="information row">
                                                <div class="seven columns"><h3>Login</h3></div>
                                            <div class="five columns"></div>
                                            </div>
                                            <div class="row" id="admin_1">
                                                <div class="information twelve columns">
                                                    <form name="loginForm" id="loginForm" enctype="application/x-www-form-urlencoded" action="<cfoutput>#cgi.SCRIPT_NAME#</cfoutput>" method="post">
                                                        <div class="five columns">
                                                            Login Name: <input type="text" name="loginName" id="loginName" style="width: 300px;" />
                                                        </div>
                                                        <div class="five columns">
                                                            Password: <input type="password" name="loginPW" id="loginPW" style="width: 300px;" /><input type="hidden" name="redirect" id="redirect" value="<cfoutput>#variables.redirect#</cfoutput>" />
                                                        </div>
                                                        <div class="two columns">
                                                            <div>
                                                                <input type="button" class="button-primary" name="submitBtn" id="submitBtn" value="Login" />
                                                            </div>
                                                        </div>
                                                    </form><cfoutput>#session.loggedin.errMsg#</cfoutput><cfset session.loggedin.errMsg = "" />
                                                </div>
                                                <div class="information"></div>
                                                <div class="spacer30"> </div>
                                            </div>
                                        </div>
                                    </div>
                                    <cfinclude template="#request.webRoot#footer.cfm" />
                                    

                                     

                                    When a login is successful, this page (the login form) will be displayed, instead of being redirected to #variables.redirect#.  The session variables _are_ being set; but for some reason, the onRequestStart isn't seeing it, and just loads z_login.cfm.  Even if a new CFID and CFTOKEN are set, the two CFHEADER tags should redirect to where the user wants to go.  At this point, I can click on any page in the navigation and that page will successfully load, so I _am_ logged on.  But the "successful-login-redirect" isn't working.

                                     

                                    Any thoughts appreciated.

                                     

                                    V/r,

                                     

                                    ^_^

                                     

                                    UPDATE:  The 'footer.cfm' contains JavaScript and jQuery.  I am using JavaScript to submit the form when the Login button is clicked.  I am not using AJaX, I am using "form.submit()".

                                    • 15. Re: Best practices for secure login of website/app
                                      WolfShade Level 4

                                      I hate to bump my thread, but this is still an issue and I have not, yet, been able to nail down any kind of reason as to why it's happening.  Login successful doesn't redirect to where it should redirect; it stays on the login form page.  If more code is needed to help determine the cause, just ask.

                                       

                                      V/r,

                                       

                                      ^_^

                                      • 16. Re: Best practices for secure login of website/app
                                        EddieLotter Level 3

                                        Can you replicate the problem in a separate, small, test application?

                                         

                                        Cheers

                                        Eddie

                                        • 17. Re: Best practices for secure login of website/app
                                          WolfShade Level 4

                                          If I get a chance to do so, today, I'll try to re-create it, here at work.  But it seems simple enough.  Application.cfc onRequestStart() params the session.loggedin variable that will contain the array of logged in information; if one of the variables has no length and the requested page isn't login.cfm or logout.cfm, redirect to login.cfm.  When the form is submit(), check the database; if the record exists and the password matches, set the session.loggedin values and redirect to another page.  Even though all the correct values are in the session.loggedin variable, the redirect does not happen and the login form is displayed.  The difference is that I have it set so that when the user is logged in, a message appears in the upper-right corner indicating the last login date/time of the user.

                                           

                                          V/r,

                                           

                                          ^_^

                                          • 18. Re: Best practices for secure login of website/app
                                            Steve Sommers Level 4

                                            I have not thoroughly looked at this code but a couple things jump out.

                                             

                                            1) Your redirect using the unvalidated http_referer or unvalidated form.redirect variable is very dangerous as it's susceptible to XSS attacks.

                                             

                                            2) I use cflocation instead of cfheader but assuming the cfheader code is correct I think you need a cfabort immediately following otherwise processing continues, including the possibliity of other cfheader calls that overwrite your settings.

                                            • 19. Re: Best practices for secure login of website/app
                                              WolfShade Level 4

                                              !  Had not thought about the CFABORT.  I'll see if that works.  Thank you!

                                               

                                              The cfheader was a suggestion I found online from someone who pointed out that CFLOCATION would issue a new CFID and CFTOKEN, thus breaking the session scope after the credentials were verified.

                                               

                                              V/r,

                                               

                                              ^_^

                                              • 20. Re: Best practices for secure login of website/app
                                                Steve Sommers Level 4

                                                I'm not sure where the "CFLOCATION would issue a new CFID and CFTOKEN" came from but I use it all the time, almost exclusively without issue. The FW/1 framework that I use does use the cfheader logic and I simply assumed it was more efficient or something but I have never had an issue CFLOCATION.