I´m not very familiar with security, therefore I rely on what I find on the internet. I found a site of someone who explains a bit what he does and how his method works. People may copy-paste it to ease things up.
Though I do understand quite a lot, I couldn't come up with it myself (I'm pretty new to PHP/XHTML, etc.)
The website: How to store safely with PHP and MySQL
He uses PDO in his tutorial. And I am able to store the information in the database. But when I try to use the script in which he provides the code for actually logging in, though it seems it contains errors.
I've worked everything out and everything works fine, but the comparison of the hashed password with the inserted password (with the hash, etc.) does not work properly.
What is going on here?
Thanks in advance!
EDIT
People have been asking for the code so, here it is:
session_start();
require('config.php');
// Setting up a connection
$MyConnection = new PDO('mysql:host=*;dbname=*', $dbuser, $pass);
// Retrieving information from form.
$username = $_POST['username'];
$password = $_POST['password'];
$sth = $MyConnection->prepare("SELECT * FROM AMP_Users WHERE Username = :username LIMIT 1");
$sth->bindParam(':username', $username);
$sth->execute();
$user = $sth->fetch(PDO::FETCH_OBJ);
// Hashing the password with its hash as the salt returns the same hash
if (crypt($password, $user->hash) == $user->hash) {
echo 'You are now logged in. If we actually used sessions this time.';
}
I will add a $_SESSION['name'] = $username, once the code starts to work. Until now I simply echo out if it worked out or not. And it doesn't show anything, so it doesn't work.
SECOND EDIT
Just as a quick update, the script provided by me, is the WHOLE script. Nothing is let out. (Except names of databases, etc.)
Therefore I wonder if the problem may be that I don't use the hashing script of the saving the passwords into the database. Though I have put it in, it still doesn't respond. Am I still doing something wrong?
if($_POST):
$name = $_POST['username'];
$pass = crypt($_POST['password'], '$2a$07$Hd893nD39Jdjd48Jdh3nD$');
$conn = new PDO('mysql:host=*; dbname=*', 'root', '');
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$stmt = $conn->prepare('SELECT * FROM user WHERE name = ? AND password = ?');
$stmt->execute(array($name, $pass));
if($stmt->rowCount() === 0){
echo 'Your Username / Password is incorrect. Please try again';
}else{
echo 'login success';
}
endif;
Maybe you have to check the length of the field that you store the password on the database... If the length is small then the hashed password will not stored as whole.. you will store a part of it!
I modified it to run in mysqli and it works fine:
$getAuth=$dbConAU->prepare("SELECT Password FROM Users WHERE UserName=? LIMIT 1");
$getAuth->bind_param("s",$UserName);
$getAuth->execute();
$getAuth->bind_result($hash);
$getAuth->fetch();
$getAuth->close();
if (crypt($Password, $hash) == $hash) {
return "OK";
}
else { return "Not OK"; }
I know this thread is a few months old but someone might find this
SunnyTuts php pdo login and registration tutorial
tutorial helpful. I found both this thread and the tutorial while looking for a secure way to allow users to login. Being new to php and web design I found it a small bit hard to follow but I'm sure it will seem like a piece of cake to some of you....
Related
I've currently been pulling my hair out on this problem for about a full day now. I am developing my first application. I'm actually pretty far into it (having established the login page several weeks ago, worked perfectly). Last night, I ran my app and it worked perfectly. I backed it up to my flash drive, tested it once more, and closed my laptop down for the night.
This morning I opened up the app to begin the documentation process on it -- this is a project for my capstone so I have to document/video the outcome each week -- and suddenly my tester account couldn't be authenticated. I tried registering a new one via the app, and it doesn't post the data to the database. Naturally, I assumed the database/server was having issues and contacted my host; in a two-hour long process, they verified that the database was working and it was simply in my code which hadn't been touched since the night before when it ran perfectly.
I've read that this sometimes happens with authentication through FireBase (i believe this is what it is called) and OAuth. I'm using neither and just doing the basic database reading/writing. I've not found any useful information on what to do through Google, probably due to the fact that the problem is just too complicated for a quick search... or I just don't know how to word it correctly.
<?php
$connection = mysqli_connect("localhost", "SENSORED", "SENSORED", "SENSORED");
$username = $_POST["username"];
$password = $_POST["password"];
$statement = mysqli_prepare($connection, "SELECT * FROM user WHERE username = ? AND password = ?");
mysqli_stmt_bind_param($statement, "ss", $username, $password);
mysqli_stmt_execute($statement);
mysqli_stmt_store_result($statement);
mysqli_stmt_bind_result($statement, $user_id, $name, $username, $email, $company_id, $phone, $password, $leaveTime, $sickTime, $rateOfPay);
$response = array();
$response["success"] = false;
while(mysqli_stmt_fetch($statement)){
$response["success"] = true;
$response["user_id"] = $user_id;
$response["name"] = $name;
$response["username"] = $username;
$response["email"] = $email;
$response["company_id"] = $company_id;
$response["phone"] = $phone;
$response["password"] = $password;
$response["leaveTime"] = $leaveTime;
$response["sickTime"] = $sickTime;
$response["rateOfPay"] = $rateOfPay;
}
echo json_encode($response);
?>
I did set up a connection test, which verified that the PHP is indeed connecting to the database and querying it. It can tell how many tables I have and, for example, how many usernames I have in the user table. However, when I enter:
"SELECT * FROM user WHERE username = 'test'"
being that there is a username that is just 'test', it cannot find a match. However, I can see from looking at the database that test is indeed a username and does exist in that table.
I currently am getting no results via PHP. However, if I query via PHPmyadmin, it displays the row that contains 'test' as a username. Obviously, my coding is correct, but I'm not sure what could be interfering with selecting it from the PHP since it is establishing a connection and reading from the database. Not to mention the fact that it isn't wanting to write the registration information to the database when there are no holds on that.
I've gone as far in looking for a solution as creating a whole new database with different credentials and such and receive the same problem. Google has been no help in the search for an answer, and I haven't managed to find a similar question/problem on here.
Any idea how a working database connection and reading/writing abilities can completely disappear without being edited overnight?
this is how I would do it. As I said i haven't used MySqli in a number of years so I had to do some googling, and I am not 100% sure everything is correct as I can't really test it.
But here goes.
//turn on output buffering - useful for debugging
ob_start();
//I would use the object interface
$mysqli = new mysqli("localhost", "SENSORED", "SENSORED", "SENSORED");
//your not checking the connection success
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
$username = $_POST["username"];
//are you storing passwords in plain text?
$password = password_hash($_POST["password"]);
/*
I would not pick the password from the DB, the DB is case insensitive,
unless you use collation UTF8_bin or such.
Also it's harder to debug, as you don't know if the user exists or the
password matching is wrong.
*/
$stmt = $mysqli->prepare($connection, "SELECT * FROM user WHERE username = ?");
//bind the inputs
$stmt->bind_param("s", $username);
//execute the query
$res = $stmt->execute();
//fetch results as an associative array
$row = $res->fetch_assoc();
//set default
$response["success"] = false;
/*
We expect exactly 1 row be returned. More then 1 should
be impossible if your username is unique (which is should be)
but just in case any issues happen we should check that it's exactly
1 return row, no more no less.
*/
if($res->num_rows() != 1) {
//always be cordial in your error messages to users
$response['message'] = "We're sorry we could not find user {$username}";
return $response;
}
//check the password hash using an approved crypto-logically safe method
if(!hash_equals($password, $$row['password']) {
$response['message'] = "Unfortunately the password you entered is incorrect";
return $response;
}
//remove the password / we no longer need it.
//security is paramount, no need to risk exposing it, even when its hashed
//eg. I just spent the weekend fixing 5 wordpress sites that were hacked
unset($row['password']);
$response['message'] = "Success!";
//put the contents of the buffer into debug. only do this on localhost
//you could switch this using the HOST or server IP
$response['debug'] = ob_get_clean();
//combine the row with the response
//caution if key matched in rows exists in response value in row
//will be replaced with that of response ( it shouldn't be a problem in this case )
$response += $row;
//set the correct content type, this allows browsers to
//know this is json, and prevents having to do JSON.parse() in some cases
header('Content-type:application/json');
//encode the responce
echo json_encode($response);
I put some notes in there such as this
//always be cordial in your error messages to users
There was once a young web developer who had a client that complained his error messages were to "cold". But this young web developer took his clients criticism to heart and remembered to always be extra nice to end users. ( that was around 2009). That said, it hasn't improved my spelling.
One thing I ran across while refactoring this, is I noticed you are not encrypting the password. I don't know if this is by mistake or by design.
ob_start and ob_get_clean allow you to echo or print_r stuff between them without breaking the JSON response. Normally if you create output then it will be outside of your json data
here is some output
{"success" : false}
Which will cause errors on the client side. With output buffering the above will look like this
{"debug" : "here is some output", "success" : false}
I suppose it's optional, but it's the right way to do JSON or even any content type other than HTML, including (or especially) file downloads.
Hope it helps. Even if you cant use the code in this form, it may help seeing it written a different way. Sometimes you can be to close to the problem and overlook some simple mistake. Looking at it in another format and having to process the logic of it, may shed light on something you missed.
After successfully verifying username exists in the database, I need to verify whether the password matches as well. Therefore, I need to retrieve the hashed password from the database, right? Or do I have other choices?
I'm using PHP 5.5 API bcrypt for hashing. My code would give me a 500 internal server error when I get the password from database. How do I do this correctly?
Here's my php code:
// If found username, check password, else respond invalid username
if($countRows!==0){
// Get hash from database
// Prepare statement
$stmt = $conn->prepare('SELECT password FROM users WHERE username = ?');
// Bind
$stmt->bind_param('s', $ps_username);
// Set Parameters
$ps_username = $username;
// Execute
$hash = $stmt->execute();
// Check password
if (!password_verify($password, $hash)) {
if($error != ""){
$error .= '<br>';
}
$error .= 'The user or password you entered do not match, please try again.';
}
else {
echo 'OK';
// Session start
// Redirect user to profile/homepage
}
}
And can someone recommend something that I can learn SQL commands? I can't find a good place for that.
execute() does not return any column data. It returns a boolean (true/false). This is where your code block first fails:
$hash = $stmt->execute();
You can view examples on how to fetch data from the result set here: http://php.net/manual/en/mysqli-stmt.fetch.php
An example being:
$stmt->execute();
$stmt->bind_result($hash);
$stmt->fetch();
In response to:
And can someone recommend something that I can learn SQL commands?
This is pretty much off topic for Stackoverflow but the PHP manual for mysqli can show you how to use the mysqli API fairly well with plenty of examples. If you want to learn the Structured Query Language itself, then there are plenty of external resources for that, including MySQL's documentation.
I'm using the following:
Firefox 31.0 (Cookies disabled)
WAMP Server 2.
The problem is that, even if I'm using the correct login account, the browser would just redirect me back into to the login page.
<?php
if (isset($_POST['login']))
{
session_start();
$username = isset($_POST['username']);
$password = isset($_POST['password']);
$con = mysql_connect('localhost','root',"") or die ("Connection Failed.");
mysql_select_db('abel', $con) or die ("Database connection Error.");
if (empty($_POST['username']) || empty($_POST['password']))
{
header ('Location: login.php');
die();
}
$result=mysql_query("SELECT * FROM users WHERE username= '".$username."' AND password='".$password."'");
$row = mysql_fetch_assoc($result);
if(mysql_num_rows($result) != 0)
{
//echo "GOOD";
$_SESSION['SES_ID'] = $row['user_id'];
$_SESSION['SES_UNAME'] = $row['username'];
header('location: home.php');
die();
}
else
{
//echo "BAD";
header('location: login.php');
die();
}
}
?>
The problem (I think) if not on the query is on the If Else. It skips the statements under IF and just jumps directly to the ELSE statement, which redirect me back to the login page, again.
The code has no errors, but maybe, logically wrong, or is it the browser. Appreciate your help.
$username = isset($_POST['username']);
$password = isset($_POST['password']);
This sets username and password to merely a boolean that indicates whether the posted values are there or not. Since you use these variables in the query, the query will fail (unless you have a user '1' with password '1').
A possible, but not very good, way to fix it would be:
$username = $_POST['username'];
$password = $_POST['password'];
But then you are open to SQL Injection, since anyone could just post any character to break your query. What if a password contains a ;, or worse: a partial SQL statement?
So even better is:
$username = mysql_real_escape_string($_POST['username']);
$password = mysql_real_escape_string($_POST['password']);
mysql_real_escape_string will escape invalid characters, so they can be used quite safely in a query.
But then, still, you are using an outdated, deprecated set of functions to connect and work with MySQL. Therefor, please read the warning on the MySQL introduction page (which is also repeated on just about every other page related to this library).
This extension is deprecated as of PHP 5.5.0, and is not recommended
for writing new code as it will be removed in the future. Instead,
either the mysqli or PDO_MySQL extension should be used. See also the
MySQL API Overview for further help while choosing a MySQL API.
So far for MySQL. Next chapter is sending and storing passwords. You should do this preferably over HTTPS (maybe you do already, I hope so). And as a rule, you should never store passwords as plain text in a database. Actually you should not store them encrypted either. You should hash them. And even then, add some salt.
The exact details are to long to explain here, but there are good explanations for this. Here is a random one: Serious Security: How to store your users' passwords safely
First problem:
$username = isset($_POST['username']);
$password = isset($_POST['password']);
isset returns a boolean. So further on in your script where you use $username it is either true/false and not the submitted data.
Security concern:
You are not escaping user submitted data when passing it to the SQL statement opening you up to SQL injection. If you insist on using mysql then I would suggest adding before your MySQL query:
$username = mysql_real_escape_string($_POST['username']);
$password = mysql_real_escape_string($_POST['password']);
Setting the return value of isset function in the var replaces original value you posted through browser.
$username = isset($_POST['username']);
And I recommend you to use better approach to SQL handling like using PDO extension.
EDITThanks to the comments below it has been figured out that the problem lies with the md5, without everything works as it should.
But how do i implent the md5 then?
I am having some troubles with the following code below to login.
The database and register system are already working.
The problem lies that it does not find any result at all in the query.
IF the count is > 0 it should redirect the user to a secured page.
But this only works if i write count >= 0, but this should be > 0 , only if the user name and password is found he should be directed to the secure (startpage) of the site after login.
For example root (username) root (password) already exists but i cannot seem to properly login with it.
<?php
session_start();
if (!empty($_POST["send"]))
{
$username = ($_POST["username"]);
$password = (md5($_POST["password"]));
$count = 0;
$con = mysql_connect("localhost" , "root", "");
mysql_select_db("testdb", $con);
$result = mysql_query("SELECT name, password FROM user WHERE name = '".$username."' AND password = '".$password."' ")
or die("Error select statement");
$count = mysql_num_rows($result);
if($count > 0) // always goes the to else, only works with >=0 but then the data is not found in the database, hence incorrect
{
$row = mysql_fetch_array($result);
$_SESSION["username"] = $row["name"];
header("Location: StartPage.php");
}
else
{
echo "Wrong login data, please try again";
}
mysql_close($con);
}
?>
The best thing you can do in such situations is trying to find out where the problem lies.
So, you could proceed by steps and do the following:
1) start your script with a print_r($_POST), to see what variables are passed by post (by absurd, the problem might even be related to the 'send' guard parameter you have ..IE a form being sent through get)
2) Assign your query to a variable (...and don't forget to escape parameters!) and print it to screen; and then exec it on mysql (or phpmyadmin) to see what results they give.
As a side note, as someone already pointed out, this code might be subject to SQL-injection, so you might consider using prepared statements; see here: quick intro
Your login code is good.
But you also need to use md5() function BEFORE storing the password in the database. So when you are inserting the user record in the DB , apply the md5() to the password , save in the DB. Now when you will try to find the record on login, it will match correctly.
You should rewrite this with mysqli or PDO and using a newer hash function as well as a salt. MD5 is very widely used and is a target for crackers.
Can anyone see anything wrong with this login script:
public function login($username, $pass, $remember) {
// check username and password with db
// else throw exception
$connect = new connect();
$conn = $connect->login_connect();
// check username and password
$result = $conn->query("select * from login where
username='".$username."' and
password=sha1('".$pass."')");
if (!$result) {
throw new depException('Incorrect username and password combination. Please try again.');
} else {
echo $username, $pass;
}
To explain:
At the moment the script is allowing anything through. In other words the query is returning true for any username and password that are passed to it.
I've put the echo statement just as a check - obviously the script would continue in normal circumstances!
I know that the connect class and login_connect method are working because I use them in a register script that is working fine. depException is just an extension of the Exception class.
The function login() is part of the same class that contains register() that is working fine.
I know that the two variables ($username and $pass) are getting to the function because the echo statement is outputting them accurately. (The $remember variable is not needed for this part of the script. It is used later for a remember me process).
I'm stumped. Please help!
UPDATE
Thanks for those responses. I was getting confused with what the query was returning. The complete script does check for how many rows are returned and this is where the checking should have been done. Everything is now working EXCEPT for my remember me function. Perhaps someone could help with that?!?! Here is the full script:
public function login($username, $pass, $remember) {
// check username and password with db
// else throw exception
$connect = new connect();
$conn = $connect->login_connect();
// check username and password
$result = $conn->query("select * from login where
username='".$username."' and
password=sha1('".$pass."')");
if (!$result) {
throw new depException('Incorrect username and password combination. Please try again.');
}
if ($result->num_rows>0) {
$row = $result->fetch_assoc();
//assign id to session
$_SESSION['user_id'] = $row[user_id];
// assign username as a session variable
$_SESSION['username'] = $username;
// start rememberMe
$cookie_name = 'db_auth';
$cookie_time = (3600 * 24 * 30);*/ // 30 days
// check to see if user checked box
if ($remember) {
setcookie ($cookie_name, 'username='.$username, time()+$cookie_time);
}
// If all goes well redirect user to their homepage.
header('Location: http://localhost/v6/home/index.php');
} else {
throw new depException('Could not log you in.);
}
}
Thanks very much for your help.
UPDATE 2!
Thanks to your help I've got the main part of this script working. However, the remember me bit at the end still doesn't want to work.
Could someone give me a hand to sort it out?
$username, $pass and $remember are all short variable names that I assigned before passing them to the function to save writing $_POST['username'] etc. everytime. $remember refers to a checkbox.
What does $conn->query() return, a MySQL resource object like mysql_query() does? If so then it'll always compare "true". mysql_query() only returns FALSE if the query completely fails, like it has a syntax error or a table doesn't exist.
To check if you got any results you need to try to fetch a row from the result set and see if you get anything, via whatever your equivalent of mysql_fetch_row() is.
Important: Your script is vulnerable to SQL injection attacks, or even just odd usernames like o'neil with an apostrophe. You should escape all variables in a query with mysql_real_escape_string() (or equivalent) to make sure your query doesn't get messed up by special characters. Or, even better, use prepared statements which look like
select * from login where username=? and password=sha1(?)
Re: UPDATE
Variables from a form are available via either $_GET or $_POST, depending on which method was used to submit the form. Try if (isset($_POST['remember'])) to see if that check box was checked.
Important: I see that you tried to use a bare $remember to see if the check box was checked. That suggests to me that you are trying to take advantage of the register_globals feature in PHP which makes your GET and POST variables accessible via regular variable names. If that is the case you should heed the warning in the PHP manual!
WARNING
[register_globals] has been DEPRECATED as of PHP 5.3.0 and REMOVED as of PHP 6.0.0. Relying on this feature is highly discouraged.
Use $_GET and $_POST instead. I could tell you how to make if ($remember) work, actually, but given the inherent evil-ness of register_globals I'm not gonna! ;-)
Your query is open for sql-injections...
SELECT * FROM users WHERE
username = '' OR 'a' = 'a'
AND password =
sha1('guessAnyPassword')
I'd also check your result, and base the action on how many records were returned.
if (mysql_num_rows($result) > 0)
In php most queries only return False if there was an error executing them. Your query is returning a value, probably an empty array of values. This is not a false value as far as your if statement is concerned.
Check how many rows are returned. The function to do this will depend on your abstraction layer (connect class etc...)