I'm new to PDO PHP (just started today). I am attempting too write a login function, but it is returning false, even though i know the credentials are correct.
I think is is the attempt to get the amount of rows which is tripping the script up, can you help?
function check_login($email, $username, $password)
{
$host = 'localhost';
$port = 3306;
$database = 'example';
$username = 'root';
$password = '';
$dsn = "mysql:host=$host;port=$port;dbname=$database";
$db = new PDO($dsn, $username, $password);
$password = md5($password);
$statement = $db->prepare("SELECT * FROM users WHERE email = ? or username = ? and password = ?");
$statement->execute(array($email, $username, $password));
while ($result = $statement->fetchObject()) {
$sql = "SELECT count(*) FROM users WHERE email = ? or username = ? and password = ?";
$result1 = $db->prepare($sql);
$result1->execute(array($email, $username, $password));
$number_of_rows = $result1->fetchColumn();
if ($number_of_rows == 1)
{
$_SESSION['login'] = true;
$_SESSION['uid'] = $result->uid;
return TRUE;
}
else
{
return FALSE;
}
}
}
This:
WHERE email = ? or username = ? and password = ?
... equals this:
WHERE email = ? or (username = ? and password = ?)
... due to operator precedence. That means that if you validate with an e-mail address, you are not required to provide a valid password to log in.
Once you've found out whether the user exists, you make a second query to count the number of matching users. The database table should not be able to hold duplicate users in the first place! Columns username and email should be defined as unique indexes.
There's no point in using a while loop if it's going to return in the first iteration. It may work, but it's confusing.
This should be enough:
$statement = $db->prepare('SELECT uid FROM users WHERE (email = ? or username = ?) and password = ?');
$statement->execute(array($email, $username, $password));
if ($result = $statement->fetchObject()) {
$_SESSION['login'] = true;
$_SESSION['uid'] = $result->uid;
return TRUE;
}else{
return FALSE;
}
Edit: BTW, you should not be storing passwords in plain text. Countless sites have been hacked and their passwords stolen. Google for salted passwords.
Related
This is probably an easy thing for you geniuses, but I have tried all of the ways I know and can't get this UPDATE Statement to work. The problem is with the update statement or the execute binding. I want the statement to add 2 points to the user's points column.
<?php
$dbConnection = new PDO('mysql:dbname=App;host=localhost;charset=utf8', '*', '*');
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$points = 2;
$username = $_POST["username"];
$password = $_POST["password"];
$response = array();
$stmt->$dbConnection->prepare("UPDATE user SET points = points + ? WHERE username = ? AND password = ?");
$stmt->execute(array($points, $username, $password));
$hi = $dbConnection->prepare("SELECT username, password, points FROM user WHERE username = ? AND password = ?");
$hi->execute(array($username, $password));
$red = $hi->fetchAll(PDO::FETCH_ASSOC);
if (count($red) > 0){
$response["success"] = true;
foreach($red as $item) {
$response["username"] = $item["username"];
$response["password"] = $item["password"];
$asd = $item["points"];
$response["points"] = (string)$asd;
}
}else{
$response["success"] = false;
}
echo json_encode($response);
?>
You need to assign $stmt to the prepared statement from the connection
$stmt = $dbConnection->prepare("UPDATE user SET points = points + ? WHERE username = ? AND password = ?");
So I'm enabling users to create accounts with a username and password. I have managed to encrypt the password when a user creates a new account using:
$hash = password_hash($password, PASSWORD_BCRYPT);
However I'm having trouble with password_verify when logging in, could someone please help me with what I have? I know it's something like this:
password_verify($password, $hash)
But I don't know how to structure it or where to add it in the code. Thanks in advance. This is what I have:
<?php
if (isset($_GET["username"]) && isset($_GET["password"]) ){
$username = $_GET["username"];
$password = $_GET["password"];
$result = login( $username, $password);
echo $result;
}
function makeSqlConnection()
{
$DB_HostName = "";
$DB_Name = "";
$DB_User = "";
$DB_Pass = "";
$con = mysql_connect($DB_HostName,$DB_User,$DB_Pass) or die(mysql_error());
mysql_select_db($DB_Name,$con) or die(mysql_error());
return $con;
}
function disconnectSqlConnection($con)
{
mysql_close($con);
}
function login($username, $password)
{
$con = makeSqlConnection();
$sql = "select * from login where username = '$username' and password = '$password';";
$res = mysql_query($sql,$con) or die(mysql_error());
$res1 = mysql_num_rows($res);
disconnectSqlConnection($con);
if ($res1 != 0) {
return 1;
}else{
return 0;
}// end else
}// end of Function
?>
The general practice is as follows:
Fetch password hash from the database where the username = the inputted username.
If rows are found, then there's a user
Now you compare the inputted password against the hash stored in the database.
I'll outline the above flow in some pseudo code for you here:
$query = SELECT password FROM users WHERE username = '$username'
$data = FETCH_THE_DATA($query);
if(password_verify($USER_INPUTTED_PASSWORD, $data['password'])) {
// password is correct
} else {
// password is in-correct
}
Notes
Stop using mysql_* functions. The library is deprecated as it's unreliable and will be removed in future releases of PHP.
You're better off using PDO or MySQLi Prepared Statements
You should always read the manual - password_verify(), it states clearly that you compare the "user inputted password" against the hashed version which is stored in your database.
Since I'm feeling good and sleepy today, I'll write a bunch of codes.
This is an example how to use PDO with prepared statement. You will have to tweak it according to your needs and you have to check if the post/get not empty as well.
I prefer to use POST request for login so this example will use POST request..
This is my user class. Which use placeholders and binding instead of passing the parameters into the query directly. This will give some protections against SQL injection attack.
class User{
private $dbh;
function __construct(){
$this->dbh = new PDO("mysql:host=".DB_SERVER.";dbname=".DB_NAME.';charset=utf8mb4', DB_USER, DB_PASSWORD) or die('db connect error');
}
function create($username, $password){
$status = false;
try{
$stmt = "INSERT INTO login (username, password)
VALUES (?, ?)";
$qry = $this->dbh->prepare($stmt);
$qry->bindParam(1, $username);
$qry->bindParam(2, $password);
$status = $qry->execute();
}catch(PDOException $e){
$e->getMessage();
}
$qry->closeCursor();
return $status;
}
public function getPassword($username){
$status = false;
try{
$stmt = "SELECT * FROM login WHERE username = ? LIMIT 1";
$qry = $this->dbh->prepare($stmt);
$qry->bindParam(1, $username);
$qry->execute();
$status = $qry->fetch(PDO::FETCH_ASSOC);
}catch(PDOException $e){
$e->getMessage();
}
$qry->closeCursor();
return $status;
}
}
This is how to create the user. Note that I don't check if the username already exist. You can either implement it yourself or use unique index on username column provided that the collation is case insensitive.
I have also increased the cost from the default that is 10 and I defined PASSWORD_DEFAULT to be used because I want the PHP engine to always use the strongest available algorithm (currently bcrypt).
function hashPassword($password){
$password = password_hash($password, PASSWORD_DEFAULT,array('cost' => 16));
return $password;
}
$user = new User;
$_POST['password'] = hashPassword($_POST['password']);
if(!$user->create(trim($_POST['username']),$_POST['password'])){
echo 'Failed creating user';
}else{
echo 'User created';
}
This is how to verify the password.
$user = new User;
$getPassword = $user->getPassword(trim($_POST['username']));
if(!$getPassword){
echo 'Error fetching user';
}else{
if(!password_verify($_POST['password'], $getPassword['password'])){
echo 'Login failed';
}else{
echo 'Login successful';
}
}
Im having a problem converting this to mysqli from mysql, i've attempted what the php documentation said but i still can't get it. Any help would be appreciated.
return (mysql_result(mysql_query("SELECT COUNT(id) FROM users WHERE (username = '$username' OR email = '$username') AND password = '$password'"), 0) == 1) ? $user_id : false;
I tried this:
return (mysqli_data_seek(mysqli_query($con,"SELECT COUNT(id) FROM users WHERE (username = '$username' OR email = '$username') AND password = '$password'"), 0) == 1) ? $user_id : false;
Whole function:
function login($username, $password){
$con=mysqli_connect("127.0.0.1","root","","frostbase");
$user_id = user_id_from_username($username);
$username = sanitize($username);
$password = md5($password);
return (mysqli_data_seek(mysqli_query($con,"SELECT COUNT(id) FROM users WHERE (username = '$username' OR email = '$username') AND password = '$password'"), 0) == 1) ? $user_id : false;
mysqli_close($con);
}
You can do it way better actually.
safeMysql is a tool that helps you to convert your ugly old mysql API-based code not to new ugly mysqli API-based code but use way better approach.
The point is that you must not use raw API calls in the application code, be it mysql, mysqli or PDO. But wrap them in a library that will do all the required operations like data binding, fetching, error handling, profiling and many more.
Frankly, you have only to write your query - the rest is done by the lib:
$sql = "SELECT 1 FROM users WHERE (username = ?s OR email = ?s) AND password = ?s";
return (bool)$db->getOne($sql, $username, $username, $password);
or, in a form of a function
function login($username, $password){
global $con; // you have to connect ONCE per application
$sql = "SELECT 1 FROM users WHERE (username = ?s OR email = ?s) AND password = ?s";
return (bool)$con->getOne($sql, $username, $username, $password);
}
I have the following function that I use to check a user login, at it's state it can only check the username and the password:
public function checkUserLogin($username, $password) {
$password = hash_hmac('sha512', $password, $this->salt($username, $password));
$sql = 'SELECT user_username,user_level FROM users WHERE user_username = ? AND user_password = ?';
// Check Login Attempts
if (isset($_SESSION['attempts']) && $_SESSION['attempts'] >= NUMBER_OF_ATTEMPTS) {
$lockdown = true;
$message['lockdown'] = true;
$message['message'] = SYSTEM_LOCKDOWN_MESSAGE;
return json_encode($message);
} else {
if ($stmt = $this->connect->prepare($sql)) {
$stmt->bind_param('ss', $username, $password);
$stmt->execute();
$stmt->bind_result($username, $admin);
if ($stmt->fetch()) {
$_SESSION['member_logged_in'] = true;
$_SESSION['username'] = $username;
$_SESSION['admin'] = $admin;
$_SESSION['attempts'] = 0;
$stmt->close();
$ip = $this->getIP();
$sql = "UPDATE users SET user_last_login_date = NOW(), user_last_login_ip = '$ip' WHERE user_username = '$username'";
if ($stmt = $this->connect->prepare($sql)) {
$stmt->execute();
$stmt->close();
} else {
$error = true;
$message['error'] = true;
$message['message'] = CANNOT_PREPARE_DATABASE_CONNECTION_MESSAGE;
return json_encode($message);
}
$error = false;
if($_SESSION['admin']==1){
$message['level'] = true;
}
$message['error'] = false;
$message['message'] = SUCCESFUL_LOGIN_MESSAGE;
return json_encode($message);
} else {
#$_SESSION['attempts'] = $_SESSION['attempts'] + 1;
$error = true;
$message['error'] = true;
$message['message'] = FAILED_LOGIN_MESSAGE;
return json_encode($message);
}
}
}
}
Making abstraction of all the functions that are included there and other variables, I need to know if I can make this function work for the username as well for the email.
What I mean is, I want to give the user the chance to login either by his / her email or by his / her username. Is that possible to do to my function, and if yes how ?
It sounds like you want WHERE (user_username = ? OR user_email = ?).
You will need to retrieve the salt from the database, though.
Best practice is to use a long sequence of cryptographically secure random bytes as salt, not the username, so you should be doing that anyway.
I suppose you could modify the query to check $username against both the username AND email fields in the database, e.g. something like
SELECT user_username,user_level
FROM users
WHERE (? IN (user_username, user_email)) AND user_password = ?';
Check for the existence of # and change your query accordingly:
if (strpos($username, '#') === false) {
$nameField = 'user_username';
} else {
$nameField = 'user_email';
}
$sql = 'SELECT user_username,user_level FROM users WHERE '.$nameField.' = ? AND user_password = ?';
The other issue is the salt being based on the username. This will obviously fail if the user decides to switch to the email address. As has been suggested, use a separate salt (unrelated to the username) and add an extra query upfront to retrieve it.
$query = "
SELECT
`user_username`,
`user_level`
FROM
`users`
WHERE
(
`users`.`user_username` = '".mysql_real_escape_string($user)."'
AND
`users`.`user_password` = '".mysql_real_escape_string($pass)."'
)
OR (
`users`.`user_email` = '".mysql_real_escape_string($user)."'
AND
`users`.`user_password` = '".mysql_real_escape_string($pass)."'
)
";
Just make the query like this. I'd advice using the mysql_real_escape_string as it makes your code better protected against SQL-injection (read-up on that!).
From this point on, you can continue with your code.
I've been modifying a user authentication system and I'm having trouble setting a session for the admin. The reguser session is setting just fine, but I can't figure out why admin won't set.
A user with a userlevel of 9 is an admin. Yes, I know how to protect against SQL injection. I'm just trying to keep it as simple and easy to read for now. This probably won't get used for anything, I'm just getting some experience with PHP.
Hi everyone, thanks for your help! I got it to work. I had been staring at it for so long that my mind wasn't clear. Took a break from it yesterday, came back to it today and was able to figure it out in less than 5 minutes! You guys are awesome, I love stackoverflow!
function checklogin($email, $pass) {
$server = 'localhost';
$user = 'root';
$password = '';
$connection = mysql_connect($server, $user, $password) or die(mysql_error());
mysql_select_db(udogoo, $connection) or die(mysql_error());
$pass = md5($pass);
$result = mysql_query("SELECT userid from users WHERE email = '$email' AND password = '$pass'");
$user_data = mysql_fetch_array($result);
$no_rows = mysql_num_rows($result);
if ($no_rows == 1)
{
$_SESSION['reguser'] = true;
$_SESSION['userid'] = $user_data['userid'];
$userid = $user_data['userid'];
$isadmin = mysql_query("SELECT userlevel FROM users WHERE userid = '$userid'");
$isadmin2 = mysql_fetch_array($isadmin);
$isadmin3 = $isadmin2['userlevel'];
if ($isadmin3 == "9"){
$_SESSION['admin'] = true;
return true;
}
}
else
{
return FALSE;
}
}
You have a return true; if the user data exists. In fact, you only check or admin-ness if the user doesn't exist.
Remove that return true;, as it's not needed there. If you want, add else return false; after the check for the user's existence, and return true; right at the end.
Your logic is flawed as well, here:
function checklogin($email, $pass)
{
$server = 'localhost';
$user = 'root';
$password = '';
$connection = mysql_connect($server, $user, $password) or die(mysql_error());
mysql_select_db(test, $connection) or die(mysql_error());
$email = mysql_real_escape_string($email);
$pass = md5($pass);
$sql = "SELECT `userid`,`userlevel`
FROM `users`
WHERE `email` = '$email'
AND `password` = '$pass'
LIMIT 1"; //I certainly hope you check email for injection before passing it here. Also want the LIMIT 1 on there because you are only expecting a single return, and you should only get one since `email` should be unique since you're using it as a credential, and this will stop it from looking through all the rows for another match once it finds the one that matches.
$result = mysql_query($sql);
$user_data = mysql_fetch_array($result);
$numrows = mysql_num_rows($result);
if ($numrows == 1)
{
$_SESSION['reguser'] = true;
$_SESSION['userid'] = $user_data['userid'];
if($user_data['userlevel'] == 9)
{
$_SESSION['admin'] = true;
}
else
{
$_SESSION['admin'] = false;
}
return true;
}
return false;
}
This should work. No good reason to do two queries when one will do just fine. Returns true if user is logged in, false if user doesn't exist or credentials don't match.
Oops, small syntax error in the SQL statement, corrected. Bigger syntax error also corrected.
And here's how you do the top part in PDO:
function checklogin($email, $pass)
{
$server = 'localhost';
$user = 'root';
$password = '';
$dbname = 'test';
$dsn = 'mysql:dbname=' . $dbname . ';host=' . $server;
$conn = new PDO($dsn,$user,$password); //Establish connection
$pass = md5($pass);
$sql = "SELECT `userid`,`userlevel`
FROM `users`
WHERE `email` = :email
AND `password` = :pass
LIMIT 1";
$stmt = $conn->prepare($sql);
$stmt->bindParam(':email',$email,PDO::PARAM_STR,128) //First param gives the placeholder from the query, second is the variable to bind into that place holder, third gives data type, fourth is max length
$stmt->bindParam(':pass',$pass,PDO::PARAM_STR,32) //MD5s should always have a length of 32
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$stmt->execute(); //almost equivalent to mysql_query
$user_data = $stmt->fetch(); //Grab the data
if(is_array($user_data) && count($user_data) == 2) //Check that returned info is an array and that we have both `userid` and `userlevel`
{
//Continue onwards
$userid = $user_data['user_id'];
$isadmin = mysql_query("SELECT userlevel FROM users WHERE userid = $userid");
$user_data = mysql_fetch_array($result);
$userlevel = $user_data['userlevel'];
if($userlevel == '9')
{
$_SESSION['admin'] = true;
}
so, your complete code look like this::
<?php
function checklogin($email, $pass)
{
$server = 'localhost';
$user = 'root';
$password = '';
$connection = mysql_connect($server, $user, $password) or die(mysql_error());
mysql_select_db(test, $connection) or die(mysql_error());
$pass = md5($pass);
$result = mysql_query("SELECT userid from users WHERE email = '$email' AND password = '$pass'");
$user_data = mysql_fetch_array($result);
$numrows = mysql_num_rows($result);
if ($numrows == 1)
{
$_SESSION['reguser'] = true;
$_SESSION['userid'] = $user_data['userid'];
//MY ANSWER START HERE
$userid = $_SESSION['userid'];
$isadmin = mysql_query("SELECT userlevel FROM users WHERE userid = $userid");
$user_data = mysql_fetch_array($result);
$userlevel = $user_data['userlevel'];
if($userlevel == '9')
{
$_SESSION['admin'] = true;
}
//END HERE
}
else
{
return false;
}
}
?>