January 3rd, 2013, 09:45 PM
Adding a Field?
Nevermind! Go ahead and delete my message. I answered my question myself.
Last edited by Faegan; January 4th, 2013 at 02:25 PM.
Reason: Should have read more first!
January 9th, 2013, 04:45 PM
If i click "Logout" on the "Private.php" page & then navigate back to domain/edit_account.php (as i could via the history) the form loads without a username, if i then enter an e-mail address and click "Update Account" i am redirected back to the "secret content" page which loads correctly.
Can anyone else do this? ... does "Logout.php" need further modifications to end the session?
January 9th, 2013, 06:58 PM
That is a bug in the edit_account.php file; it needs to have a session check at the top:
Thanks for posting regarding the problem.
// At the top of the page we check to see whether the user is logged in or not
// If they are not, we redirect them to the login page.
// Remember that this die statement is absolutely critical. Without it,
// people can view your members-only content without logging in.
die("Redirecting to login.php");
January 11th, 2013, 04:27 PM
thanks for this tutorial, successfully implemented combined with jQuery.
January 18th, 2013, 09:14 AM
forget password link
I have got this script installed and works perfect, I was just wondering how can I put a forget password link on the login page so users can enter their email address to get a new password sent to their email
Thank you in advance
January 20th, 2013, 03:14 PM
Remember Me Feature
Thank you for the excellent tutorial and thorough documentation. I learned a lot!
Has anyone successfully implemented this with a "remember me" feature? I imagine that you store the user's username and hashed password in a cookie but I wanted to make sure that's the best practice.
January 21st, 2013, 12:05 AM
The original purpose of this tutorial was actually intended to serve more as an introduction to PHP and MySQL than as a tutorial on how to build a login system; however, there does seem to be significant demand for the latter. The problem was that so many of the "PHP+MySQL" tutorials that you see online are terribly insecure, and since they are aimed at complete beginners the person following the tutorial obviously won't know that.
So, at some point in the future I will probably expand this and possibly turn it into a two-parter, one for the basic PHP+MySQL introduction and the other with more detailed information on building a login system.
From a beginner's standpoint the most difficult part of this task will probably be sending the email. Although PHP has a built-in mail function, I recommend using a library like PHPMailer or Swift Mailer. PHPMailer is a bit easier to understand, although Swift Mailer is a bit more modern. Those libraries will take care of the complicated part of sending emails for you.
It's not a good idea to just reset the user's password and send them a new one. This has a few security problems with it.
The most straight forward approach is to email the user a reset password URL when they click the forgot password link. When the user visits the URL, they are able to enter a new password. To do this, you would need to add two columns to your users database table. One to store a reset password token (to use in the URL) and another to store an expiration date for a reset password token. For security reasons, you should not make the token's value forever; a few hours at most should be sufficient unless you have a really slow mail server.
(Note: a "token" in this case just means a long unique random string of characters)
When a user requests that their password be reset, you should randomly generate a reset password token and store it in the database. The process of generating a random token is identical to the process used to generate a random salt in the register.php file. You should also store the expiration date in the database at this time as well.
You will then email the user a link that includes that token as a URL parameter. For example: http://yoursite.com/reset_password.php?token=<TOKEN VALUE HERE>
In your reset_password.php script, you would perform a look up in the database using that token (similar to the lookup performed in login.php, but using the token column rather than the username column in the WHERE clause). You would check the expiration date on the token, and if not expired, you would allow the user to choose a new password and update the database with the new value as is currently done in the edit_account.php file.
At some point I will likely incorporate this into the actual tutorial with code examples, but as that will probably be some time in the future you may want to give it a try on your own. If you run into trouble, feel free to post your code on the main PHP board for help.
You should not store the hashed password in a cookie ever. There is a thread floating around here somewhere that discusses the concepts behind a "remember me" feature in more detail, but from a high level design there are two safe approaches you can take:
(Note: a "token" in this case just means a long unique random string of characters)
(1) Using randomly generated tokens
First you would create a new database table that stores (at least) three pieces of data in each row: a remember me token, a user id and an expiration date. The remember me token column should be unique, and so can serve as the primary key for the table.
When a user logs in with a "remember me" option enabled, you would generate a new random token (the process of generating a random token is identical to the process used to generate a random salt in the register.php file), insert the token into that new database table along with an expiration date and user ID, then set a cookie on the user's computer containing that token.
When an unauthenticated user visits the site, you would look for the cookie, and if it contains a valid and unexpired remember me token, you would re-authenticate them automatically by setting the appropriate session variables (as is done in login.php).
(2) Using a hash
This approach does not require a database table, but it does require you to define (and keep secret) a secret key. You can define it in common.php. The secret key should be a long random value.
When a user logs in with a "remember me" option enabled, you store data into the cookie in plaintext. You need to store (at least) a random token, a user id, an expiration date, and a hash. The hash is used to validate the other pieces of information and verify that they have not been tampered with.
The easiest way to store the information in a cookie will be to serialize it using the serialize() function. This function will take a PHP array, and generate a string from it. You can later take that string and turn it back into a PHP array using the unserialize() function. In fact, for this particular application it is easiest to use serialize twice, which I will demonstrate in a moment.
You can then generate a hash using the sha256 algorithm shown in register.php, although in this case you do not need to run it multiple times.
Now, the critical piece of this is verifying the hash when you read the cookie, prior to automatically logging in the user. First you unserialize the cookie to get back your $cookie array. Then you need to re-compute $hash using $cookie['data'] and compare it against $cookie['hash']. If they match, the cookie was not altered. If they do not match, then the cookie was modified by the user. If the hashes match, then you can unserialize $cookie['data'] and use the user ID to reauthenticate the user automatically.
// An array containing the data you want to store securely in a cookie
$info = array(
// Convert that data into a string (because you can't store an array in a cookie)
$info_str = serialize($info);
// Generate a hash of the data using your secret key
$hash = hash('sha256', $info_str . $secret_key);
// The data that we will actually store in the cookie
$cookie = array(
'data' => $info_str,
'hash' => $hash,
// The string to store in the cookie
$cookie_str = serialize($cookie);
Note that in both of these methods it is a very wise idea to set the $httponly flag when you call setcookie(). This will help protect you against attackers stealing the cookie.
Another way to protect against this is to store and validate additional information about the user. For example, you can store and check the user agent string or IP address, although note that IP addresses are likely to change legitimately.
As with my previous reply, at some point I will likely incorporate this into the actual tutorial with code examples, but as that will probably be some time in the future you may want to give it a try on your own. If you run into trouble, feel free to post your code on the main PHP board for help.
Comments on this post
January 21st, 2013, 01:02 PM
Thanks for the reply. This is incredibly helpful. I appreciate you taking the time to write such a thorough walk-through of your thoughts.
January 23rd, 2013, 07:05 AM
when you update the script in the future, you might wanna fix two security issues:
- Since changing the email address or password doesn't require any confirmation, the member's account can by captured with a simple CSRF attack or by "stealing" the session ID. I'd add CSRF protection and force the user to retype the old password in both cases.
- The salt values aren't secure, because mt_rand isn't a cryptographic RNG (and was never meant to be). I generally don't find it a good idea to use self-made algorithms, because they're neither reviewed nor tested. Why not use PHPass? That would also simplify the code quite a bit.
January 23rd, 2013, 04:32 PM
What about removing a user?
Say I have an assistant admin without access to phpmyadmin or the database besides through this interface and a regular user named Joe creates an account named joee and then creates one name joe since he messed up. How hard would it be to change the registration form to a deletion form without requiring my assistant admin to know the password on the joee account?
January 23rd, 2013, 07:48 PM
That's a good idea, thanks, I'll see about incorporating this next time I do an update.
That's true (about mt_rand not being a cryptographic RNG), but for the salt values this isn't a major issue. The point of a cryptographic RNG is to prevent giving the attacker any assistance in determining what value the generator may have produced at a given time; but for a salt this doesn't matter because the salt still accomplishes its purposes even if the attacker knows its value. The salt values just need to be relatively unique and relatively long, which mt_rand accomplishes in a way that is portable (and therefore shorter and easier to explain in the tutorial).
However, since mt_rand is not a cryptographic RNG, it would not be a good idea to use it for generating encryption keys. It's also not really a great idea to use it for generating tokens as I suggested in my last post, but because those are time-limited it's less of an issue there.
PHPass is a good library and it would simplify the code, but it does so by abstracting away several key concepts that I wished to discuss in the tutorial; particularly the way salts actually work in conjunction with a hash function.
The scenario you're talking about gets a little more complicated because now you need to deal with who has permission to delete accounts. However, the operation to actually delete an account is fairly straight forward. It's more like the update page than the registration page, but instead of running an UPDATE query you need to run a DELETE query, and the only input you need is the ID of the user to delete.
January 24th, 2013, 08:15 AM
Well, I guess it's really a problem of the tutorial aspect clashing with the practical use. I understand you want to demonstrate the concept of salts, iterative hashing etc., but many people will just copy and paste the code to use it for their login logic (because that's what the title says).
Anyway, I'm currently working on a class to wrap all user related functionalities in convenient methods (register, login, logout etc.). Maybe that would be a good addition to the tutorial.
As to the salt: A "predictable" salt reduces the range of all possible values, so it's basically like truncating the overall length. I can't tell you how bad that is with regard to the Mersenne twister, because I'm not a cryptographer. But what I do know is that the OWASP specifically recommends cryptographically secure salts and that PHPass (as well as other implementations) reads the salt from /dev/urandom and only uses built-in functions as a fallback.
January 26th, 2013, 02:56 AM
getting a errror on the post How to program a basic but secure login system using PHP
it is an unknown error to me
please kindly help me out
when i fill out the registeration form and submit the data it appears
Failed to run query: SQLSTATE: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'use WHERE username = 'sam123'' at line 3
if any one have any idea please let me know
January 26th, 2013, 11:22 AM
Check to make sure you have correctly copied the query that checks whether the username is already in use on the registration page. No where in your code should you have the words "use" and "where" next to each other, which is what the error message is saying you have. Possibly you have "use where" instead of "users where".
"use" is a reserved SQL keyword, so using it incorrectly in that context would result in a syntax error. However, normally if you typed the table name incorrectly you would get an error about a missing table.
January 27th, 2013, 07:18 AM
Im still stuck on the forgot password coding
I got a forgot password script off the internet but keep getting the following error with it
Login Failed. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
Please help me