This question already has answers here:
How can I prevent SQL injection in PHP?
(27 answers)
Closed 4 years ago.
I am learning how to use prepared statements in my simple login system to make it more secure.
I have followed a few different tutorials to get it working but cant get it to work. When i enter the username and password wrong it gives me the error. When i enter the username and password correct i still get the error.
What am i doing wrong?
I am new to this so apologies for any obvious errors.
I have also looked into hashing my password as it is being stored as plain text in the database at the moment but that will be my next step after i get this working.
Here is my code:
<?php
error_reporting(E_ALL); ini_set('display_errors', 1);
session_start(); // Starting Session
$error=''; // Variable To Store Error Message
if($SERVER['REQUESTMETHOD'] == 'POST') {
if (empty($POST['username']) || empty($POST['password'])) {
$error = "Enter Username and Password";
}
else
{
// Define $username and $password
$username = $_POST['username'];
$password = $_POST['password'];
//connect to database
include('dbconx.php');
}
$stmt = $con->prepare("SELECT * from admin where password=? AND username=?");
$stmt->bind_param('ss', $username, $password);
$stmt->execute();
$stmt->bind_result($id, $username, $password);
$stmt->store_result();
if($stmt->num_rows == 1) //To check if the row exists
{
$_SESSION['login_user'] = $username; // Initializing Session
header("location: confirm.php"); // Redirecting To Other Page
}
else {
$error = "Username or Password is incorrect";
}
mysqli_close($con); // Closing Connection
}
?>
You have your bound parameter arguments backwards. Your query binds password then username but your bind_param() uses $username then $password.
I've never been a fan of using the number of rows returned to determine existence. Instead, you can simply use fetch(). It will return a boolean value indicating whether or not there was a result.
For example
$stmt = $con->prepare('SELECT id from admin where password = ? AND username = ?');
$stmt->bind_param('ss', $password, $username); // note the order
$stmt->execute();
$stmt->bind_result($id);
if ($stmt->fetch()) {
$_SESSION['login_user'] = $username;
$_SESSION['login_user_id'] = $id; // probably important
header("Location: confirm.php");
exit; // always exit after a "Location" header
} else {
$error = "Username or Password is incorrect";
}
mysqli_stmt::store_result should be called before mysqli_stmt::bind_result, also you would need to call mysqli_stmt::seek_data and mysqli_stmt::fetch to get the result.
Example :
<?php
$db = new Mysqli(...);
$inputUsername = $_POST['username'] ?? '';
$inputPassword = $_POST['password'] ?? '';
$statment = $db->prepare('SELECT `id`,`username`,`password` FROM `admin` WHERE `username` = ?');
$statment->bind_param('s',$inputUsername);
$statment->execute();
$statment->store_result();
$statment->bind_result($id,$username,$password);
if($statment->num_rows) {
$statment->data_seek(0);
$statment->fetch();
/**
* this is not secure
* #see http://php.net/manual/en/function.password-hash.php
*/
if($inputPassword === $password) {
echo sprintf('Welcome, %s!',$username);
} else {
echo 'Incorrect password!';
}
} else {
echo sprintf('No such user with the given username (%s)',$inputUsername);
}
$statment->close();
$db->close();
Removed bind_result and store_result for get_result and fetch_assoc. It makes getting db records more flexible and stable.
Also added exit(); after redirection so no other codes will be executed after redirect command.
Typo in:
if (empty($POST['username']) || empty($POST['password']))
^ $POST should be $_POST instead.
$error is not being checked properly if empty or not. And still goes through mysqli functions block even if there is an error. Fixed that by creating an appropriate if statement that encloses the mysqli funtions block.
Also added proper indentation to the code for readability.
New Code:
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
session_start(); // Starting Session
$error=''; // Variable To Store Error Message
$_POST['username'] = isset( $_POST['username'] ) ? $_POST['username'] : '';
$_POST['password'] = isset( $_POST['password'] ) ? $_POST['password'] : '';
if($_SERVER['REQUEST_METHOD'] == 'POST') {
if (empty($_POST['username']) || empty($_POST['password'])) {
$error = "Enter Username and Password";
}
else{
// Define $username and $password
$username = $_POST['username'];
$password = $_POST['password'];
//connect to database
include('dbconx.php');
}
if( $error == "" ) {
$stmt = $con->prepare("SELECT * from students where username=? AND password=?");
$stmt->bind_param('ss', $username, $password);
$stmt->execute();
$result = $stmt->get_result();
if($result->num_rows == 1) {
$row = $result->fetch_assoc();
$_SESSION['login_user'] = $row['username']; // Initializing Session
header("location: confirm.php");exit(); // Redirecting To Other Page
}
else {
$error = "Username or Password is incorrect";
}
mysqli_close($con); // Closing Connection
}
echo $error;
}
?>
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
i have a query using PDO. password match was successful when I enter only strings or number. But when my password contains #& or anything like that it will tell that password is incorrect. Though in my database that was the right password.
session_start();
// Change this to your connection info.
$DATABASE_HOST = 'localhost';
$DATABASE_USER = 'root';
$DATABASE_PASS = '';
$DATABASE_NAME = 'Data-Six';
// Try and connect using the info above.
$con = mysqli_connect($DATABASE_HOST, $DATABASE_USER, $DATABASE_PASS, $DATABASE_NAME);
if ( mysqli_connect_errno() ) {
// If there is an error with the connection, stop the script and display the error.
die ('Failed to connect to MySQL: ' . mysqli_connect_error());
}
// Now we check if the data from the login form was submitted, isset() will check if the data exists.
if ( !isset($_POST['username'], $_POST['password']) ) {
// Could not get the data that should have been sent.
die ('Please fill both the username and password field!');
}
// Prepare our SQL, preparing the SQL statement will prevent SQL injection.
if ($stmt = $con->prepare('SELECT `used-id`, `username`, `password` FROM `user-list` WHERE `username` = ?')) {
// Bind parameters (s = string, i = int, b = blob, etc), in our case the username is a string so we use "s"
$username = $_POST['username'];
$password = $_POST['password'];
$stmt->bind_param('s', $username);
$stmt->execute();
// Store the result so we can check if the account exists in the database.
$stmt->store_result();
}
if ($stmt->num_rows > 0) {
$stmt->bind_result($username, $password, $id);
$stmt->fetch();
// Account exists, now we verify the password.
// Note: remember to use password_hash in your registration file to store the hashed passwords.
if ($_POST['password'] === $password) {
// Verification success! User has loggedin!
// Create sessions so we know the user is logged in, they basically act like cookies but remember the data on the server.
session_regenerate_id();
$_SESSION['loggedin'] = TRUE;
$_SESSION['name'] = $_POST['username'];
$_SESSION['id'] = $id;
echo 'Welcome ' . $_SESSION['name'] . '!';
} else {
echo 'Incorrect password!';
}
} else {
echo 'Incorrect username!';
}
$stmt->close();
?>
The order of variables in bind_result doesn't follow the order of field names in the SQL query.
That said, store_result/bind_result is outdated and inconvenient method which was replaced by get_result that gets you a conventional PHP array.
Here is the code you need:
$sql = 'SELECT `used-id`, `username`, `password` FROM `user-list` WHERE `username` = ?';
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
// if ($_POST['password'] === $password) { come onm you MUST use a hash
if ($user && password_verify($_POST['password'], $user['password']))
{
...
}
as you can see it is much more concise and convenient
I just figured out how to used the PDO statement XD! thanks team. what i did was just rearranged the query to match the Bind-result.
if ($stmt = $con->prepare('SELECT `username`, `password`, `used-id` FROM `user-list` WHERE `username` = ?')) {
$username = $_POST['username'];
$password = $_POST['password'];
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->store_result();
}
if ($stmt->num_rows > 0) {
$stmt->bind_result($username, $password, $id);
$stmt->fetch();
if ($_POST['password'] === $password) {
session_regenerate_id();
$_SESSION['loggedin'] = TRUE;
$_SESSION['name'] = $_POST['username'];
$_SESSION['id'] = $id;
echo 'Welcome ' . $_SESSION['name'] . '!';
echo 'Welcome ' . $_SESSION['id'] . '!';
} else {
echo 'Incorrect password!';
}
} else {
echo 'Incorrect username!';
}
This question already has an answer here:
What to do with mysqli problems? Errors like mysqli_fetch_array(): Argument #1 must be of type mysqli_result and such
(1 answer)
Closed 3 years ago.
I'm trying to implement a login system, and it mostly works except for this PHP script that's been returning 0:
// $username_err & $password_err is empty, gets reset every time as "".
if(empty($username_err) && empty($password_err)){
$query = "SELECT rowid, username, password FROM admin_login WHERE username = ?";
if($stmt = mysqli_prepare($db, $query)){
mysqli_stmt_bind_param($stmt, "s", $param_username);
$param_username = $username;
if(mysqli_stmt_execute($stmt)){
mysqli_stmt_store_result($stmt);
// Doesn't work here, works up until here.
if(mysqli_stmt_num_rows($stmt) == 1){
mysqli_stmt_bind_result($stmt, $rowid, $username, $hashed_password);
if(mysqli_stmt_fetch($stmt)){
if(password_verify($password, $hashed_password)){
session_start();
$_SESSION["loggedin"] = true;
$_SESSION["id"] = $rowid;
$_SESSION["username"] = $username;
header("location: index.php");
}else{
$password_err = "Invalid password";
}
}
}else{
$username_err = "No such account exists.";
}
}else{
echo "An error occurred.";
}
}
mysqli_stmt_close($stmt);
}
mysqli_close($db);
This never works, as it gets stuck since it doesn't meet the condition mysqli_stmt_num_rows == 1, rather it returns a 0. It does work via MySQL directly through the Workbench:
SELECT rowid, username, password FROM admin_login WHERE username = "admin";
does return 1 row with the matching criteria.
** EDIT: Here's my new code; **
<?php
require_once "_php/login/config.php";
$username = $password = "";
$err = "";
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (empty(trim($_POST["username"]))) {
$err = "Enter a username";
} else {
$password = trim($_POST["password"]);
}
if (empty(trim($_POST["password"]))) {
$err = "Enter a password";
} else {
$password = trim($_POST["password"]);
}
$stmt = $db->prepare("SELECT rowid, username, password FROM admin_login WHERE username = ?");
$stmt->bind_param("s", $_POST["username"]);
$stmt->execute();
$stmt->store_result();
$user = $stmt->get_result()->fetch_assoc();
if ($user && password_verify($_POST["password"], $user["password"])) {
session_start();
$_SESSION["loggedin"] = true;
$_SESSION["username"] = $username;
header("location: index.php");
} else {
$err = "Wrong password";
}
}
//end of program
And it throws:
Fatal error: Uncaught Error: Call to a member function fetch_assoc() on boolean in directory/login_script.php:25 Stack trace: #0 directory/login.php(2): require() #1 {main} thrown in directory/login_script.php on line 25
For reference, login_script is the PHP script, login is the frontend as php.
You never need that useless num_rows thing. You already have a row from fetch(). Use it wherever you have an idea to use num_rows.
When it's 0 (or, rather, the only fetched row is false), it means your query found no rows given the input values and the current database contents. As simple as that. This is the answer to your question. Simple, direct and logical.
That said, this code three times more bloated than it needs be. See the canonical code I wrote for checking the password using mysqli. Verify your input values, the database contents and check for errors - and it would work flawless.
I created a login page using mysql with user role, but login page not working/not loading.
Here is the code:
<?php
include "config.php";
if (isset($_POST['username']) && isset($_POST['password'])){
$username = $_POST['username'];
$password = md5($_POST['password']);
$stmr = db->prepare("SELECT * FORM login WHERE username=? AND password=?");
$stmt->bindParam(1, username);
$stmt->bindParam(2, password);
$stmt->execute();
$row = $stmt->fetch();
$user = $row['username'];
$pass = $row['password'];
$id = $row['id'];
$type = $row['type];
if ($username==$user && $pass==$password) {
session_stat();
$_SESSION['username'] = $user;
$_SESSION['password'] = $pass;
$_SESSION['id'] = $id;
$_SESSION['type'] = $type;
if($type=='Member'){
?>
<script>window.location.href="index.php"</script>
<?php
} else {
?>
<script>window.location.href="index.php"</script>
<?php
}
} else {
?>
<strong>Oops!</strong> You Can not Not Visit This Page.
</div>
<?php
}
}
?>
I don't understand where is the problem. Please help me to solve this problem.
____ Advance Thanks And All The Best.
You have a couple of syntax errors on your code here are they :
$stmt->bindParam(1, username);
$stmt->bindParam(2, password);
$type = $row['type];
session_stat();
Security Vulnerability md5() more especially that you are doing all this in a production server, you should use password_hash() and password_verify()` available on the php.net documentation.
and many more errors I just found as I look in your code, better add this line while you are still developing ` on top of every page so you can see all your errors, when you done on live server then remove that line. look at my comments for more errors.
This is how your login page should look with no errors;
<?php
ob_start();
session_start();
if (isset($_SESSION['username'])) {
header("location:dashboard.php"); // if the user is logged in already dont log themm in again, its gonna irritate them.
}
include "config.php";
if (isset($_POST['SubmitButtonName'])) {
if (empty($_POST['username']) || empty($_POST['password'])) {
//return error to the user
} else {
$username = $_POST['username'];
$password = $_POST['password'];
try {
$stmt = $db->prepare("SELECT * FROM login where username = ? ");
$stmt->execute([$username]);
$results = $stmt->fetchall(PDO::FETCH_ASSOC);
if (count($results) > 0) {
//username is correct
foreach ($results as $key => $row) {
//now verify password
if (password_verify($password, $row['password'])) {
// password is correct
$_SESSION['username'] = $row['username'];
$_SESSION['id'] = $row['id'];
if ($row['type'] == "Member") {
header("location:page.php");
} else {
//type not a member redirect to different page
header("location:AppropriatePage.php");
}
} else {
//password incorrect return proper message
}
}
} else {
//user account does not exists return proper message
}
}
catch (PDOException $ex) {
error_log("Error : " . $ex->getMessage());
}
}
}
?>
NB: On your registration page remove this line $password = md5($_POST['password']); and replace with $password =
Password_hash($_POST['password'],PASSWORD_DEFAULT); and then
re-register when you are done you can login again.
Read Jay Blanchard's blog about proper password preparation in php
here :
http://www.jayblanchard.net/proper_password_hashing_with_PHP.html
then for pdo prepared statements read Your Common Sense's blog here :
https://phpdelusions.net/pdo
change session_stat();
to session_start()
and make sure you on your display errors in php.ini file
Problem is in many typos and syntax errors and that is reason why your page not loading. Here fixed typos and syntax:
include "config.php";
if (isset($_POST['username']) && isset($_POST['password'])){
$username = $_POST['username'];
$password = md5($_POST['password']);
$stmt = $db->prepare("SELECT * FROM login WHERE username=? AND password=?"); //changed
$stmt->bindParam(1, $username); // changed
$stmt->bindParam(2, $password); // changed
$stmt->execute();
$row = $stmt->fetch();
$user = $row['username'];
$pass = $row['password'];
$id = $row['id'];
$type = $row['type']; /// you forgot \' here
if ($username==$user && $pass==$password) {
session_start(); // changed
$_SESSION['username'] = $user;
$_SESSION['password'] = $pass;
$_SESSION['id'] = $id;
$_SESSION['type'] = $type;
if($type=='Member'){
I'm performing a query to check if a user exists before adding it to the database. If that result comes back then die and echo 'username already exists' but if it comes back empty then add the new user to the database.
For some reason it just adds a new user to the database anyway.
//If post was
if (isset($_POST['submit'])) {
// Check if username is blank
if (!isset($_POST['username']) || empty($_POST['username'])) {
echo "Username was blank<br />";
die();
} else {
$username = mysqli_real_escape_string($connection, $_POST['username']);
}
// Check if password is blank
if (!isset($_POST['password']) || empty($_POST['password'])) {
echo "Password was blank<br />";
die();
} else {
$password = mysqli_real_escape_string($connection, $_POST['password']);
$password2 = md5($password);
//echo $password;
}
// Check if email is blank
if (!isset($_POST['email']) || empty($_POST['email'])) {
echo "Email was blank<br />";
die();
} else {
$email = mysqli_real_escape_string($connection, $_POST['email']);
//$password = md5($password);
//echo $password;
}
//Check to see if username alread exsists
$query_check = "SELECT * FROM users WHERE user = '$username' LIMIT 1";
$result_check = mysqli_query($connection, $query_check);
if(count(mysqli_fetch_array($result_check)) === 1) {
echo "Username exists.";
die();
} else {
$query = "INSERT INTO users (user, pass, email) VALUES ('$username','$password2','$email');";
$result = mysqli_query($connection, $query);
if($result){ // returned TRUE, e.g. in case of a DELETE sql
$_SESSION["username"] = $username;
header("Location: ../profile.php");
} else { // returned FALSE
//echo "Error: " . mysqli_error($connection);
echo "Error during register <a href='../register.php'>Back To Register</a>";
die();
}
}
} else {
header("Location: ../index.php");
}
After taking a few minutes testing your code, found that you're using the wrong function.
mysqli_fetch_array():
Fetch a result row as an associative, a numeric array, or both
You're trying to fetch an associative array.
As opposed to mysqli_num_rows():
Gets the number of rows in a result
Replace (and which seems to have been taken from FĂ©lix's answer)
if(count(mysqli_fetch_array($result_check)) === 1)
with
if(mysqli_num_rows($result_check) == 1)
or
if(mysqli_num_rows($result_check) > 0)
Your original post contained:
if(mysqli_fetch_array($result_check) === 1)
which still stands to be the wrong method.
I even said to use mysqli_num_rows() in a comment, but nothing was said about it:
if(mysqli_num_rows($result_check) >0) and make sure $username is defined. We don't know how/where if it is even defined.
Now, if THAT fails, then your form element isn't named, and/or something else in your form is failing you.
I.e.: <input type="text" name="username">
Add error reporting to the top of your file(s) which will help find errors.
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
// rest of your code
Sidenote: Error reporting should only be done in staging, and never production.
Regarding using MD5.
That isn't considered safe to use anymore, as far as password hashing goes.
That technology is old and is considered broken.
For password storage, use CRYPT_BLOWFISH or PHP 5.5's password_hash() function.
For PHP < 5.5 use the password_hash() compatibility pack.
Pulled from ircmaxell's answer which uses PDO with prepared statements and password_hash():
Just use a library. Seriously. They exist for a reason.
PHP 5.5+: use password_hash()
PHP 5.3.7+: use password-compat (a compatibility pack for above
All others: use phpass
Don't do it yourself. If you're creating your own salt, YOU'RE DOING IT WRONG. You should be using a library that handles that for you.
$dbh = new PDO(...);
$username = $_POST["username"];
$email = $_POST["email"];
$password = $_POST["password"];
$hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $dbh->prepare("insert into users set username=?, email=?, password=?");
$stmt->execute([$username, $email, $hash]);
And on login:
$sql = "SELECT * FROM users WHERE username = ?";
$stmt = $dbh->prepare($sql);
$result = $stmt->execute([$_POST['username']]);
$users = $result->fetchAll();
if (isset($users[0]) {
if (password_verify($_POST['password'], $users[0]->password) {
// valid login
} else {
// invalid password
}
} else {
// invalid username
}
Footnotes:
I noticed you are using headers.
You should add exit; after each header. Otherwise, your code may want to continue executing.
header("Location: ../profile.php");
exit;
and do the same for the other one also.
You're also using sessions. session_start(); isn't present in your posted and will fail if it isn't included; an insight.
here
if(mysqli_fetch_array($result_check) === 1) {
the value returned by mysqli_fetch_array won't be an integer but an array. You seem to want to count it:
if(count(mysqli_fetch_array($result_check)) === 1) {
In the case somehow two users would have been inserted for whatever reason, checking if count is greater than 0 may prevent a third one being inserted:
if(count(mysqli_fetch_array($result_check)) > 0) {
I'm trying to change my code from mysql_connect to PDO and I'm having some problems. I'm new to PHP and I was told that mysql_connect was old so here is my code. I can't seem to validate the user. Even If I enter a wrong user, it does not output the invalid user.
$_GET['errorCode'];
$errors = array(
1 => 'Invalid User',
2 => 'Incorrect Password');
if(isset($_GET['errorCode'])){
$code = $_GET['errorCode'];
print isset($errors[$code]) ? $errors[$code] : 'Unknown error';
}
if($_SERVER['REQUEST_METHOD']== 'POST'){
$username = $_POST['username'];
$password = $_POST['password'];
if(isset($_POST['username']) && isset($_POST['password'])){
$stmt = $dbh->prepare("SELECT password, salt FROM user WHERE username = :user_name");
$stmt->execute(array(':user_name' => "$username"));
$stmt->bindParam(':user_name', $username, PDO::PARAM_STR);
if(!$stmt->rowCount() > 0)
{
header('Location: index.php?errorCode=1');
exit;
}
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$hash = hash('sha256', $result['salt'] . hash('sha256', $password) );
if($hash != $result['password']) //incorrect password
{
header('Location: index.php?errorCode=2');
exit;
}
else
{
validateUser(); //sets the session data for this user
Header("Location: index.php");
exit;
}
}
}
You forgot to execute the statement - you just need to run $stmt->execute(); after binding the params.
-- Edit --
And delete the line: $userData = $dbh->query($stmt);; your row will be fetched into $result with your current code.
Apart from the missing execute statement, this is never going to work:
Header("Location: index.php");
$errmessage = "Invalid user";
print "<p id\"errmessage\"> $errmessage </p>";
exit;
As soon as you redirect, the browser will load index.php so you will never see your error message. You should or redirect or show an error message, but not both.