Create User Accounts

Discussion in 'Scripts, 3rd Party Apps, and Programming' started by ParallelLogic, Jul 19, 2011.

  1. ParallelLogic

    ParallelLogic New Member

    I'm looking to create a site that a user can log into. I have a modest programming background, but have not worked with cookies or SSL before. I won't be hosting this portion of my project on x10 (this will be on GoDaddy), so I have some flexibility in what I can do.

    I feel that what I am looking to do is quite common, so if you know of a tutorial that covers what I'm about to explain, I'd greatly appreciate a link to it (my searches of the net haven't been very fruitful). This x10 forum is a good model for what I'm trying to do - I'd like a user to be able to create an account (once they've filled in a user name, password and email, they should get an email to verify their email address), and then once they have an account, they should be able to log into the account (and view various pages specific to them, which I presume involves cookies). Later on I'll be looking to add a cart to the site (so individual users can purchase items), so I'd be interested in any tips on SSL as well.

    My primary goal is to set up a site that a new user can create an account on, get an email, verify their email address through the email, and then log into their account through my website.
  2. essellar

    essellar Community Advocate Community Support

    This is going to take a few postings to complete because of the character count limit. Plus there's quite a bit of information involved, and I'm a horribly slow typist, so please be patient.

    What you are looking for is very common indeed, and it's almost a blessing that you don't know the magic incantation to Google up a quick solution. You would think that because user logins are so common that it would be a solved problem, but it's unlikely that that will ever actually be the case. The best we can ever do is "good enough for now" -- and most of the solutions you will find on the internet weren't really good enough for 1995. Frankly, most of the solutions in production on web sites that absolutely need to be secure are nowhere near good enough for use on a simple hobby site, let alone for your online banking account. So let's put you in the above-average group, and I'll start with a bit of explanation.

    First, you must never know your users' passwords. If you store the passwords, even if you store them encrypted ("encrypted" means that they can be "decrypted"), then you've lost the game before it even starts. That means that you can never tell the users their passwords if they forget them, of course, but it also means that a hacker can't find a way to grab your database and get a complete list of user names, email addresses and passwords. So what? Your site isn't really all that important. Perhaps not, but people tend to use the same one or two passwords all over the internet, so a hacker that manages to get your database of users probably has their email accounts, online bank accounts, and so forth. If you get it wrong, it puts your users at a lot of risk.

    Passwords should be stored using a one-way "hash" function. The same data will always result in the same value using the same hash algorithm, but there's no way to work backwards from the hash value to the original data -- you have to use brute force, trying different passwords until you come up with a match. There is a problem with using a simple hash, though -- someone can use a "rainbow table", which is basically a pre-calculated database of passwords and their hashes, if they know the hash algorithm you're using. The most common "secure" (take that with a grain of salt) login scripts on the web use a simple MD5 hash algorithm, and comprehensive rainbow tables for MD5 have been available in the darker corners of the web for a decade or more.

    That "grain of salt" is part of the key to making it better. A "salt" is an additional value you add on to the user's password before hashing. It makes rainbow tables useless, especially if you use a different salt (nonce) value for every user. You don't need to get too very sophisticated with the salt; I usually use an MD5-hashed version of the time at which the account was created. An MD5 hash is 32 characters long, so you'll need a char(32) not nullable column in your login table to store the salt.

    ---------- Post added at 07:20 AM ---------- Previous post was at 07:20 AM ----------

    (Part II)

    That leaves only one major outstanding problem: hash functions are too darned efficient these days. If you are using even one of the better hash functions (like SHA256), and your user database goes astray, it's only a matter of hours before the bad guys have your users' passwords. Hours are not good enough; you need to make it into months or years. That's where we make a deliberate decision to put our usual love for efficiency and speed aside and deliberately choose to use a slow and awkward method. Probably the best method to use is something called bcrypt, but that relies on your host having a certain PHP extension installed, so it's not always something you have access to. Luckily, there is something almost as slow and ugly as bcrypt that you can almost always rely on being able to use, and that's PBKDF2 (Password-based Key Derivation Function 2, which is an IETF standard: RFC 2898):

    PHP:
    <?php
      
    // PBKDF2 Implementation (described in RFC 2898)
      
    function pbkdf2($password$salt$iter_count=1000$key_length=256$algorithm 'sha256'$start_pos=0)
      {
        
    $key_blocks $start_pos $key_length;
        
    $derived_key '';
        for (
    $block=1$block<=$key_blocks$block++)
        {
          
    $iterated_block $current_hash hash_hmac($algorithm$salt pack('N'$block), $passwordtrue);
          for (
    $i=1$i<$iter_count$i++)
          {
            
    $iterated_block ^= ($current_hash hash_hmac($algorithm$current_hash$passwordtrue));
          }
          
    $derived_key .= $iterated_block;
        }
        return 
    substr($derived_key$start_pos$key_length);
      }
    ?>
    As written here, it uses the SHA256 hash algorithm (unless you tell it to do otherwise) a thousand times (again, unless you feed it a different, and preferably larger, number to use), so that few hours it would otherwise take to get your users' passwords has turned into months at best. By default, it returns a 256-character string as the "key". That sounds like a lot, but one of the vulnerabilities of a hash algorithm is something called a "collision", the possibility that two different inputs can result in the same output. If database space is at a premium, you can reduce the $key_length value, but it really takes an awful lot of users before that should be a problem, so I'd suggest leaving it alone. That means you need a char(256) not nullable column to store the password key.

    While it is okay to enforce a minimum length for a password for the users' own protection, that should really be the only thing you enforce. If the users can't remember their passwords because of your rules, they're likely to write them down somewhere, and that becomes its own vulnerability. Again, since users tend to use the same password in multiple places, you may be exposing them to danger elsewhere. And since you're storing exactly 256 characters of gobbledegook no matter what their password is, if they want to use the first chapter of Harry Potter and the Half Blood Prince as their password, it's no skin off of your nose. You still check pbkdf2($password, $salt) against what was stored in the database.

    Your user table will also need a column for the username (a varchar(32) should do, but you can make it longer if people are using their formal names rather than "handles") and an email address (this can be long, so a varchar(256) might be in order). Both of those columns should enforce unique values and should not be nullable. You are probably going to want the users to be able to change their usernames and email addresses at some point, but you'll still want to be able to connect the user to other data you're storing elsewhere (profile info, avatars, that sort of thing), so you'll also want to have a user_id column, which can be a number that auto-increments with each account created.

    There are still a couple of columns to go in the login table. You'll want a status column to indicate when there is a pending action (like an account confirmation or a password reset) or if the account has been banned/closed. That can be an integer. You'll also need a column to store the key for a pending action -- that will be part of the URL of the link you send to the user to confirm their account or to reset their password. I'd suggest storing the key using the pbkdf2 function as well, just in case, so the column would be a nullable char(256).
    Last edited by a moderator: Jul 19, 2011
  3. essellar

    essellar Community Advocate Community Support

    (Part III)

    Grrrr -- my dislike for automated emoticons grows ever more towards hatred. That "cool dude" should have been an 8 and a closing parenthesis; the standard is RFC 2898.

    You may want to add one last column to the login table, and that would be to store a time value for when the pending action was created. It's optional, but you may want to be able to make the links you send your users "stale" if they haven't been used within a certain period of time. You'll be clearing the pending action status and the action key once the link is used (to prevent someone else from using, say, the reset password link to hijack the account), so it's only a one-time deal, but you probably don't want the link to stay active for any length of time. The user can always make the request again if necessary.

    That about does it for the database end of things. Well, except for one thing -- you definitely don't want to use PHP's mysql functions to read from or write to the database. Doing that can result in this:

    [​IMG]

    (Image courtesy of XKCD, used in accordance with the Creative Commons Attribution/Non-Commercial 2.5 License)

    Instead, use the PHP Data Objects (PDO) class and its methods and prepared statements. That way, if little Bobby Tables does decide to join your site (or if someone decides to use a SQL statement as a password), you're still good to go. Sure, the script kiddies will think you're no fun at all, but you shouldn't feel responsible for their self-esteem in any case.

    You now have enough information in hand to go searching for "PHP login script" +email +PDO and fix what you find. If you need more help, let us know. You don't need to worry about cookies, the PHP session structure will take care of that automatically for you. I would avoid any "remember me" functions that work by storing the username and password in a cookie, since cookies are readable. If a bad guy manages to hijack a user's session, he should only be hijacking a session and (possibly) screwing up the user's account -- he shouldn't be able to get hold of the user's password by listening in.

    As for the SSL stuff, there's nothing to it. You have your hosting provider install a certificate, then you make your users use https: instead of http: -- the rest happens automagically. If you are using SSL, then all of your user logins should also use https:. That closes the last hole in the fence.
  4. callumacrae

    callumacrae not alex mac Community Support

    You can find an example of an auth library I wrote a while back on my GitHub: https://github.com/callumacrae/lynx-framework/blob/master/lynx/plugins/auth/auth.php - use it to work out how I did it and base yours on mine :)

    I'm going to advertise myself some more - I have also written a tutorial on the basics of PDO - http://lynxphp.com/php/pdo-basics/


    vBulletin appears to have started stripping control characters, too :-(


    *snigger*

    ~Callum
  5. essellar

    essellar Community Advocate Community Support

  6. callumacrae

    callumacrae not alex mac Community Support

    Yes, I am.

    I started reading that websites about page, but stopped after the first sentence:
    I'm not storing it in plain text.
    Even if I was storing it in plain text, it is not there, "waiting for someone to come and take it".
    Not *once* does that website actually say why emailing the passwords is a bad thing.


    Let's face it, half the internet either: (a) uses the same password for everything, or (b) has all their passwords written down on a file in their computer. If you're in the other half, I don't see why you can't just delete the email.

    ~Callum
  7. misson

    misson Community Paragon Community Support

    Further down on the page you mention (the about page) is a link to a FAQ: "What's so wrong about sending a new password in plaintext?". The fact is many (even those in the second half) won't delete e-mail with passwords, even if the e-mail says to delete it. For those who aren't security conscious, we must be security conscious on their behalf.
  8. essellar

    essellar Community Advocate Community Support

    Exactly. Callum, I looked over your code, and all I could see to legitimately complain about was the email part. You have no idea how far up the ladder that puts you -- I usually come over all orangutan-with-a-toothache after looking at login code, and that's for some pretty sensitive stuff (originally military applications, then things like medical records and finance). I've been farting around with a "portable" PKI setup (sort of like the Lotus Notes ID, which is about as good as it's ever gotten, but without the need to rely on being able to install a file or relying on something like SmartCard slots being available). Looks like Mozilla has been working on the same thing with BrowserID, so maybe the time has come.
  9. callumacrae

    callumacrae not alex mac Community Support

  10. essellar

    essellar Community Advocate Community Support

    There, now that's something I can really get all huffy about -- kinda makes one want to lobby for some sort of compulsory computing licenses. If only LulzSec would go after sites like these...
  11. callumacrae

    callumacrae not alex mac Community Support

    I can't find a single line of code that I wouldn't complain about on that login script :D
  12. essellar

    essellar Community Advocate Community Support

    Well, to be fair, the author did use <?php instead of <?, but there's always the chance that he (I can't believe a she would have been proud of that) wasn't aware that there was a worse way.
  13. callumacrae

    callumacrae not alex mac Community Support

    They used <? once too. All short tags is better than inconsistencies!
    Last edited: Jul 20, 2011
  14. iearn.tk54

    iearn.tk54 New Member

    better try
    smf forum
    vbb
    phbb
    or wordpress
    direct softwares .......... i just go easy/.............your choice
  15. callumacrae

    callumacrae not alex mac Community Support

    ...?

    That's completely unrelated and not very helpful.

Share This Page