For some reason, this query is running case-sensitive:
$stmt = $db->prepare("SELECT * FROM people WHERE email = :email LIMIT 1");
It simply returns whether or not it found the user:
$stmt->bindParam(':email', $email);
$stmt->execute();
$row = $stmt->fetch();
if($row['email'] == $email)
{
return "<span style='color: red;'>User found.</span><br>";
} else {
return "<span style='color: red;'>User not found.</span><br>";
}
(By the way, this is all just staging. There will be password hashing as soon as I see this is working properly).
It finds the user no problem if I use the same case as the database entry.
Here is my table, so you can see it's all defined ci:
This is actually an existing site that I built when I didn't know much about php, so I'm totally re-writing a lot, and setting up proper password hashing and https. This was all working fine before I wrote the new function and nothing in the database has changed...
So right now it only checks the email entered, just to see if the query is functioning and we're getting results from the database, later on we'll check the password and add actual login functionality.
Here is the data in the database:
Now if I fill out my username as "chris", and run the function, it returns "User Found", so I know the query was successful. If I fill it in as "Chris", however, it returns "User not found.", so I know it was unsuccessful.
Found the issue, though. Posted as an answer.
The issue is with:
if($row['email'] == $email)
The "==" comparison of the strings is case sensitive. A better way to do this would be to use:
if(!empty($row['email']))
If $row['email'] is not empty, then the query returned a result and was successful, else the query failed, which would be caused by using an email address which does not match any in the database.
To make the string comparison case inssensitive requires one of three approaches
Use lower (this will effect performance)
$stmt = $db->prepare("SELECT * FROM people WHERE LOWER(email) = LOWER(:email) LIMIT 1");
Another way is to use collation
And a way that I use is that I store the email as lower case to begin with and convert the search string to lower before doing the search.
Related
Context
I'm trying to implement a (hopefully) simple login system using PHP and PostgreSQL.
This is the postgres table containing usernames and hashed passwords.
Users are not meant to be able to create new rows in this table so I already hashed the passwords using password_hash('password', PASSWORD_BCRYPT) and then manually copypasted the value in the table.
Let me know if you think this could pose a problem.
Users can, however, login to an already existing account by inputting the right username and password combination into a login form.
When the login button is pressed I need to retrieve information about the user entered, so that if it matches any user I can then verify the password using password_verify().
The Problem
When the login button is clicked I run this code:
$dbconn = pg_connect("host=host dbname=dbname user=user password=pwd");
if (!$dbconn) {
die("Error in connection: " . pg_last_error());
}
// setting the username as it would be provided by the form field
$username = 'Dan';
// maybe unrelated: why do I need to write 'username' instead of username? all of my other queries work without ''
$query = "SELECT * FROM user WHERE 'username' = $1";
$stmt = pg_prepare($dbconn, "", $query);
var_dump($stmt); // gives output "resource(3) of type (pgsql result)", got no clue on how to see what's indside
$result = pg_execute($dbconn, "", array($username));
var_dump($result); // gives output "resource(4) of type (pgsql result)"
$row = pg_fetch_assoc($result);
var_dump($row); // gives output "bool(false)", indicating that pg_fetch_assoc() failed
The main problem would be that pg_fetch_assoc() fails and returns false, however I believe this may also be caused by an incorrect query or by the way I build the statement, hence why I included everything.
Edit
Forgot to mention that I also tried formulating the query as:
$query = "SELECT * FROM user WHERE username = $1";
And in this case I get and error saying:
Warning: pg_prepare(): Query failed: ERROR: column "username" does not exist LINE 1: SELECT * FROM user WHERE username = $1.
Thanks to Don't Panic's comment I renamed my user table to utiliser and everything worked as it should have.
It should also be noted that this could be circumvented by using double quotes on the table name (according to this answer), however, double quotes are evil so better to just stay away.
Here's the full table of reserved words to avoid as table/column names.
Hello StackOverflow community,
I am working on a registration system for my own use and I am trying to program it using prepared statements. Since that's recommended.
So let me explain a bit about the structure of my table.
Every user has a row called confirm which is of type enum and can be either 0 or 1.
0 - account not confirmed
1 - account confirmed
I think you can see where I'm going with this.
Simply want to check based on a query if that column is 0 or 1.
Here is a piece of my code.
$sql = "SELECT username,email FROM users WHERE username=? OR email=? AND confirm=? LIMIT 1";
$stmt = mysqli_stmt_init($conx);
if(!mysqli_stmt_prepare($stmt,$sql)) exit(mysqli_error($conx));
else {
$confirm = 0;
mysqli_stmt_bind_param($stmt,"ssi",$usernameEmail,$usernameEmail,$confirm);
mysqli_stmt_execute($stmt);
$count = mysqli_stmt_num_rows($stmt);
if($count == 1) exit("Please confirm your account before logging in.");
else exit("Ok continue ...");
In my Database I have 1 confirmed account and one not confirmed account to be able to test both cases.
PHP keeps on going to the else line even tho the account is not confirmed.
The user can use either his or her email or username to log in.
I am rather new at when it comes to prepared statements.
Sorry if i didn't nail the formatting right away. My first post on stack overflow :)
Your help would be much appreciated!
Cheers,
Andi
Your query needs a little help with formatting to select the correct record
// put parens around your OR
$sql = "SELECT username,email FROM users WHERE (username=? OR email=?) AND confirm=? LIMIT 1";
And, since your confirmed column is an enum you can't use i in your bind. Your bind needs to use 'sss' instead of 'ssi'.
mysqli_stmt_bind_param($stmt,"sss",$usernameEmail,$usernameEmail,$confirm);
I'm currently working on building a download platform in which a user receives a random code and uses it to access an mp3 for download for up to three downloads. I generated a list of random codes using Python and imported them into a SQL table with an empty column for an associated email addresses and a default 0 for the use count. I wrote the following PHP script in order to associate an email with a certain code and add to the count so a download can be accessed up to three times.
$email = $_POST["email"];
$email = stripslashes($email);
$uniqueCode = $_POST["uniqueCode"];
$uniqueCode = stripslashes($uniqueCode);
// check that all fields are filled
if($uniqueCode=="" || $email=="")
apologize("Please fill out all fields.");
// check to make sure that the e-mail is valid
if (verifyEmail($email) == FALSE)
apologize("Please enter a valid e-mail address.");
// check if uniqueCode input is alphanumeric
if (verifyCode($uniqueCode) == FALSE)
apologize("Download codes are alphanumeric.");
// check to see if unique code is correct
$sql = mysql_query("SELECT * FROM wd009 where uniqueCode='$uniqueCode'");
$result = mysql_fetch_array($sql);
if($sql==FALSE)
{
apologize("Your download code is invalid. Please try again");
}
// only allow users with less than 3 downloads to proceed
else if ($result['count'] <= 3) {
if ($result['email'] == ""){
mysql_query("UPDATE wd009 SET email='$email', count=1 WHERE uniqueCode='$uniqueCode'");
apologize("added email");
}
else if ($result['email'] != $email)
apologize("different email from record!!");
else if ($result['email'] == $email){
mysql_query("UPDATE wd009 SET count=count+1 WHERE uniqueCode='$uniqueCode'");
apologize("updated the count!");
}
else
apologize("Your download code is used up!");
Obviously I use some functions in the above that aren't included in the code but I've checked all of them and none of them should interfere with the MySQL query. It may be of note that apologize() exits immediately after apologizing. When I input a correct code into the form, it works correctly and updates the SQL database. However, as long as the download code input is alphanumeric, the form will accept it even though the string definitely does not match any in the table. Namely, mysql_query returns a resource no matter the input. I've checked the database connection but since the table is correctly updated when the download code is correct, that doesn't seem to be the problem.
I've tried debugging this every way I could think of and am genuinely befuddled. Any help you could offer would be greatly appreciated!
As you can see in the manual, mysql_query always returns a resource for a valid query so you need to change your logic and count the number of rows it returns, not the result of mysql_query.
Apart from that, mysql_query, is deprecated and you should use mysqli or PDO.
You can count the number of rows with the - equally deprecated - mysql_num_rows function. 0 rows would be no valid code in your case.
This
if($sql==FALSE)
should probably be something like
if(mysql_num_rows($sql) == 0)
Edit: I agree, mysqli or PDO is preferred now.
The issue probably is this line:
if($sql==FALSE)
{
apologize("Your download code is invalid. Please try again");
}
Since the sql is a string it is accepting is as true and passing it as valid. One thing you also might like to do to avoid sql injection is use parameters instead of directly injecting user input.
$sql = mysql_query("SELECT * FROM wd009 where uniqueCode='$uniqueCode'");
Instead, do something like this:
$stmt = $mysqli->prepare("SELECT * FROM wd009 where uniqueCode=?");
$stmt->bind_param($uniqueCode);
$stmt->execute();
while ($stmt->fetch()) {
.....
You'll also want to do it this way for the update statement too.
If you have a lot of data in that table you may want to restrict the columns returned in the SQL statement so it lessens the load on the database.
When you authenticate registered users you make request e.g. one examples i found:
$user = $_POST['user'];
$pw = $_POST['password'];
$sql = "SELECT user,password FROM users
WHERE user='$user'
AND password='$pw'
LIMIT 1";
$result = mysql_query($sql);
if (mysql_num_rows($result)){
//we have a match!
}else{
//no match
}
Now what would be the benefit or any point of having LIMIT 1 at the end?
And why you need to select user and password when you can just select user_id?
Would not the
SELECT user_id FROM users
WHERE user = '{$user}'
AND password = '{$pw}'
be same exact logistics but shorter code?
EDIT: thinking about this little detail made me find one more check to prevent hackers.
There should not be more than one user with same email and password so if they somehow supply instead of password 123 e.g. string ' OR password = '*' (or similar logics) this will compromise my query, having no limit would help because next step i can count
if (count($result) > 1) {
echo "we got hacked";
else
<proceed...>
Assuming you have only a single row in the database for each username/password pair, the LIMIT clause improves performance by discontinuing the search after the first match is found, primarily when used in conjunction with an ORDER BY clause.
From the MySQL manual:
If you use LIMIT row_count with ORDER BY, MySQL ends the sorting as soon as it has found the first row_count rows of the sorted result, rather than sorting the entire result. If ordering is done by using an index, this is very fast.
Most likely, you will have a unique user column, so LIMIT 1 is not necessary - you won't have more than 1 row anyway.
In this case, it might be a decorative element - self explaining syntax to tell a programmer that reads a code, that query is expected to return no more than one row.
Aside from your question, I would strongly recommend to use some password encryption, for example MD5(). A tutorial that teaches you to store a plain passwords is not the best one...
You can read "SQL Injection: How To Prevent Security Flaws In PHP / MySQL" and see how your login might be useless without proper measures.
Everything else was answered by far wiser posters above.
In most cases your table will hold unique usernames so you will always get back 1 or 0 rows. And as such as far as your code is concerned it doesn't make a difference. Even if you had multiple rows returned your code would still work, as you are only checking for existence of rows and not how many of them have been returned (but it would be wrong, as you wouldn't know which user actually logged in).
Basically it just tells MySQL to stop searching the table after the first row that meets the conditions in WHERE is found. In some cases even this could be redundant (if you had a UNIQUE index on the "user" field for instance).
One more thing not related to your question: please do not use this code for anything but learning. It's full of security holes. Google for "SQL injection" and "storing password securely" before you put this code into production.
Firs you need to sanitize strings from injection:
class main{
public function sanitize($str,$remove_nl=true)
{
stripslashes($str);
if($remove_nl)
{
$injections = array('/(\n+)/i',
'/(\r+)/i',
'/(\t+)/i',
'/(%0A+)/i',
'/(%0D+)/i',
'/(%08+)/i',
'/(%09+)/i'
);
$str = preg_replace($injections,'',$str);
}
return $str;
}
}
Next yours code:
$main_class = new main();
$user = $main_class->sanitize(trim($_POST['user']));
$pw = $main_class->sanitize(trim($_POST['password']));
$sql = "SELECT * FROM `users` WHERE `user`='".$user."' AND `password`='".$pw."' LIMIT 0,1";
$result = mysql_query($sql) or die(mysql_error());
$count = mysql_num_rows($result);
if($count > 0){
//we have a match!
}else{
//no match
}
I need the following authentication script finished. I am weak at php/pdo so I do not know how to ask for the number of rows equalling one and then setting the session id's from the results of the query. I need to not only set the $_SESSION['userid'] but also the ['company'] and the ['security_id'] as well from the results.
here is what I have:
$userid = $_POST['userid'];
$password = $_POST['pass'];
if ( $userid != "" || $password != "" )
{
$sql = "SELECT * FROM contractors WHERE userid = '" . $userid . "' AND password = '" . $password . "'";
$result = $dbh->query( $sql );
} else
{
echo "login failed. Your fingers are too big";
}
Optional Information:
Browser: Firefox
DO NOT EVER USE THAT CODE!
You have a very serious SQL injection open there. Every user input that you take, whether from cookies or CGI, or wherever, must be sanitized before it's used in an SQL statement. I could easily break into that system by attempting a login with an username like:
user'; UPDATE contractors SET password = '1337'
... after which I could then login as anyone. Sorry if I sound aggressive, but what that code does is like forgetting to lock the front door into your company which probably doesn't even contain an alarm system.
Note that it doesn't matter whether the input is actually coming from the user or not (perhaps it's in a pre-filled, hidden from). From the security point of view, anything that comes from anywhere outside has to be considered to contain malicious input by the user.
As far as I know, you need to use the quote function of PDO to properly sanitize the string. (In mysql, this would be done with mysql_real_escape_string().) I'm not an expert on PDO, mind you, somebody please correct if I'm wrong here.
Also you probably shouldn't store any passwords directly in the database, but rather use a hash function to create a masked password, then also create a hash from the user provided password, and match the hashes. You can use the PHP hash function to do this.
As for other issues, I don't know if the approach you have on SQL SELECT is the best approach. I would just select the corresponding user's password and try matching that in the program. I don't think there's any fault in the method you're using either, but it just doesn't seem as logical, and thus there's a greater chance of me missing some bug - which in case of passwords and logins would create a window for exploits.
To do it your way, you need to notice that the result you are getting from the PDO query is a PDOStatement, that doesn't seem to have a reliable function to diretly count the amount of result rows. What you need to use is fetchAll which returns an array of the rows, and count that. However, as I said this all feels to me like it's open for failures, so I'd feel safer checking the password in the code. There's just too much distance from the actual password matching compasion for my taste, in such a security-critical place.
So, to the get the resulting password for the userid, you can use PDOStatement's fetch() which returns the contents of the column from the result. Use for example PDO::FETCH_ASSOC to get them in an associative array based on the column names.
Here's how to fix it:
$userid_dirty = $_POST['userid'];
$password_dirty = $_POST['pass'];
$success = false; // This is to make it more clear what the result is at the end
if ($userid != "" || $password != "") {
$userid = $dbh->quote($userid_dirty);
$passwordhash = hash('sha256',$password_dirty);
$sql = "SELECT userid, passwordhash, company, security_id FROM contractors WHERE userid = ".$userid;
$result = $dbh->query( $sql );
if ($result) { // Check if result not empty, that userid exists
$result_array = $result->fetch(PDO::FETCH_ASSOC);
if ($result_array['PASSWORDHASH'] == $passwordhash) {
// login success
$success = true;
// do all the login stuff here...
// such as saving $result_array['USERID'], $result_array['COMPANY'], $result_array['SECURITY_ID'] etc.
} // else fail, wrong password
} // else fail, no such user
} else {
// fail, userid or password missing
echo ' please enter user id and password.';
}
if (!$success) {
echo ' login failed.';
}
Of course, the code can be cleaned up a bit, but that should explain what needs to be done. Note that since the password is both hashed, and never used in the SQL, it doesn't actually need cleaning. But I left it there just in case, since in the original code it was used in the query.
Note that all the code concerning storing passwords need to be changed to store the hash instead of the password. Also, it would be a very good idea to use a salt added to the password before hashing.
Also, I provided the code simply for educational purposes - I just thought that code was the clearest way to explain how to do this. So do not mistake this site as a service to request code. :)
The php manual is an excellent resource for learning PHP. It looks like you know a little SQL, and you have heard of PDO, which is a good start. If you search google for "PDO", or look in the PHP manual for the term, you'll find the PDO section of the manual. It looks like you've found the ->query function, so now you need to see what that returns. Going to the that function's manual page, we see that it returns a PDOStatement object. The word PDOStatement is helpfully linked to the relevant page in the manual, which lists the methods available on that object. There is a rowCount() method that will likely do what you want.