Here is mysql_* code:
Activation mail and hash check
PDO:
Do anyone sees the solution ?
if (isset($_GET['email']) && !empty($_GET['email']) AND isset($_GET['hash']) && !empty($_GET['hash'])){
// Verify data
$search = $db->prepare("SELECT email, hash, active FROM users WHERE email=:email AND hash=:hash AND active=0");
$search->bindParam(':email', $_POST['email'], PDO::PARAM_STR);
$search->bindParam(':hash', $_POST['hash'], PDO::PARAM_STR);
$search->execute();
//$match = $search->fetch(PDO::FETCH_ASSOC);
$match = $search->rowCount();
There is a problem in this part of condition
if($match > 0){
// We have a match, activate the account
$db->prepare("UPDATE users SET active= 1 WHERE email=:email AND hash=:hash AND active=0");
$db->bindParam(':email', $_POST['email'], PDO::PARAM_STR);
$db->bindParam(':hash', $_POST['hash'], PDO::PARAM_STR);
$db->execute();
echo '<div class="statusmsg">Your account has been activated, you can now login</div>';
}else{
// No match -> invalid url or account has already been activated.
echo '<div class="statusmsg">The url is either invalid or you already have activated your account.</div>';
}
}else{
// Invalid approach
echo '<div class="statusmsg">Invalid approach, please use the link that has been send to your email.</div>';
}
The condition finishes the code here:
The url is either invalid or you already have activated your account.
But it should finish the code here:
Your account has been activated, you can now login.
You check $_GET but later using $_POST.
You have few mistakes in your code,
First the $_POST that you are using here does not exists.
$search->bindParam(':email', $_POST['email'], PDO::PARAM_STR);
$search->bindParam(':hash', $_POST['hash'], PDO::PARAM_STR);
You should be using $_GET
rowCount returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement executed by the corresponding PDOStatement object.
Thefore rowcount, is not reliable on select,
below is working code that you can use to achieve what you looking for.
if (isset($_GET['email']) && !empty($_GET['email']) AND isset($_GET['hash']) && !empty($_GET['hash'])){
$email = $_GET['email'];
$hash = $_GET['hash'];
$search = $db->prepare("SELECT email, hash, active FROM users WHERE email=:email AND hash=:hash AND active=0");
$search->bindParam(':email', $email, PDO::PARAM_STR);
$search->bindParam(':hash', $hash, PDO::PARAM_STR);
$search->execute();
$match = $search->fetchall(PDO::FETCH_ASSOC);
if(count($match) > 0){
//then match exists activate the profile
$stmt = $db->prepare("UPDATE users SET active= 1 WHERE email= ? AND hash= ? AND active=0")->execute(array($email,$hash));
if(!$stmt){
print_r($db->errorInfo());
}else{
//account activated
echo '<div class="statusmsg">Your account has been activated, you can now login</div>';
}
}else{
// No match -> invalid url or account has already been activated.
echo '<div class="statusmsg">The url is either invalid or you already have activated your account.</div>';
}
}else{
// Invalid approach
echo '<div class="statusmsg">Invalid approach, please use the link that has been send to your email.</div>';
}
The problem in the flow is bound to this line of code:
$match = $search->rowCount();
You're executing a SELECT query and the rowCount is only available on INSERT, DELETE or UPDATE queries.
Instead, to find out if there is at leas match, you can use fetch() to get the "first" row, and if it exists, there was a match:
...
$match = $search->fetch();
if ($match) {
...
As another alternative approach to prevent that line of code you can in theory as well remove the first SELECT query in full and execute the second UPDATE query first and then check with the rowCount whether or not the user was updated.
There is more left open to comment with your code, like the logic you validate the hashes as they have no time-limit and the generation of the hash is even unknown but often critical.
Reference:
Row count with PDO
Related
I have a PHP script using PDO where I check a user's submitted email/password against a database. If the query returns a row, it is supposed to take the user to a success page, or if the credentials are incorrect they are supposed to be taken to a failed login page. However, the user is always taken to the fail page.
$sql = "SELECT email, password FROM user WHERE email= $email AND password = $password";
$stm = $db->prepare($sql);
$stm->execute();
$result = $stm->fetchColumn();
if ($result !== FALSE) {
header('Location: ./success.html');
}
else {
header('Location: ./failed.html');
}
Your original problem was simply missing quotes around the variables inserted into the query.
Just fixing that problem would leave you vulnerable to SQL injection attacks. Making proper use of statement preparation and execution will solve that problem. And never, never, store plaintext passwords. When you save them, use password_hash and then use code like this to verify them.
$password = $_POST["password"];
$email = $_POST["email"];
$sql = "SELECT password FROM user WHERE email= ?";
$stm = $db->prepare($sql);
$stm->execute([$email]);
$result = $stm->fetchColumn();
if ($result !== FALSE) {
if (password_verify($password, $result[0])) {
header("Location: ./success.html);
exit;
}
}
header("Location: ./failed.html");
More details on password hashing can be found elsewhere on SO.
And please note that for brevity I'm not checking the result of the prepare() or execute() functions. You should be doing this.
I'm trying to create email verification but for some reason, I can't update my MySQL table. I've tried various different ways of doing it but I can't seem to get it to update.
<?php
$connect = mysqli_connect("x", "x", "x", "x");
if(isset($_GET['email']) && !empty($_GET['email']) AND isset($_GET['hash']) && !empty($_GET['hash'])){
// Verify data
$email = mysqli_escape_string($_GET['email']); // Set email variable
$hash = mysqli_escape_string($_GET['hash']); // Set hash variable
$statement = mysqli_prepare($connect, "SELECT * FROM users WHERE email = ? AND hash = ?");
mysqli_stmt_bind_param($statement, "ss", $email, $hash);
mysqli_stmt_execute($statement);
mysqli_stmt_store_result($statement);
$count = mysqli_stmt_num_rows($statement);
mysqli_stmt_close($statement);
if($count < 1){
// We have a match, activate the account
$statement = mysqli_prepare($connect, "UPDATE users SET active ='1' WHERE email=? AND hash=?");
mysqli_stmt_bind_param($statement, "ss",$email, $hash);
mysqli_stmt_execute($statement);
mysqli_stmt_store_result($statement);
$count = mysqli_stmt_num_rows($statement);
mysqli_stmt_close($statement);
echo '<div class="statusmsg">Your account has been activated, you can now login</div>';
}
else{
// No match -> invalid url or account has already been activated.
echo '<div class="statusmsg">The url is either invalid or you already have activated your account.</div>';
}
}
else{
// Invalid approach
echo '<div class="statusmsg">Invalid approach, please use the link that has been send to your email.</div>';
}
?>
<!-- stop PHP Code -->
One of the probable reason of rows not getting updated because, you are checking of number of rows less than 1, which would be false, since you are expecting some rows from SELECT statement.
if($count < 1){
instead, it should be
if($count == 1){
In your IF challenge you are looking if there is NO entry in the db with that hash and rmail. But there should be one that is not yet active. So: There should be exactly 1 entry in the db.
if($count == 1){
That should do the trick.
You should further also look for
AND active=0
In your first SQL statement to avoid 2 activations or remove the hash from your db once the uset is activated.
I've done a login and registration for my site and it works fine.
Now I just want to make a simple profile page where the user can see all their details.
I'm only able to get back the username, so I'm unsure how to get the rest of their details.
Here is the code for registering and logging in:
function selectUser($conn, $username, $password)
{
$query = "SELECT password FROM login WHERE username = :username";
$stmt = $conn->prepare($query);
$stmt->bindValue(':username', $username);
$stmt->execute();
if ($row = $stmt->fetch(PDO::FETCH_OBJ))
{
if (md5($password) == $row->password)
{
$_SESSION['username'] = $username;
//$_SESSION['password'] = $password; DO NOT DO THIS
echo "Welcome, you are now logged in as " . $username;
return true;
}
return false;
} else {
//echo "Your details were not found";
return false;
}
}
function selectNew($conn, $name, $username, $password, $contact, $occupation, $role, $picture)
{
$query = "INSERT INTO login VALUES (NULL, :name, :username, :password, :contactNumber, :occupation, :role, :pic)";
$stmt = $conn->prepare($query);
$stmt->bindValue(':name', $name);
$stmt->bindValue(':username', $username);
$stmt->bindValue(':password', $password);
$stmt->bindValue(':contactNumber', $contact);
$stmt->bindValue(':occupation', $occupation);
$stmt->bindValue(':role', $role);
$stmt->bindValue(':pic', $picture);
$affected_rows = $stmt->execute();
if ($affected_rows == 1)
{
return true;
} else {
return false;
}
}
Don't worry, the password has been hashed.
heres what I've tried:
function selectUser($conn, $username, $password)
{
$query = "SELECT * FROM login WHERE username = :username";
$stmt = $conn->prepare($query);
$stmt->bindValue(':username', $username);
$stmt->execute();
$row = $stmt->fetch();
echo $row['occupation'];
echo $row['role'];
}
2nd attempt:
if(isset($_SESSION["username"]))
{
echo "Welcome, you are now logged in as <b>".$_SESSION['username']."</b> <img class='clientView' src='images/loginIcon.png' alt='client'>"; }
else {
echo "You are currently not logged in";
}
$user = $_SESSION["username"];
$query = "SELECT * FROM login WHERE username = :username";
$term = $conn->prepare($query);
$term->bindValue(':username', $user);
$term->execute();
if ($username = $term->fetch(PDO::FETCH_OBJ))
{
echo "<li><h3>" . $user->username ." ". $user->user_ID . "</h3></li>";
}
The simple answer is to replace your query in selectUser(...) with SELECT * FROM login WHERE username = :username. Note the * after the SELECT command, which functions as a wild card and thus asks for every single column of each row it finds (instead of just the password column as you are currently asking for.
You could then, as you iterate over the returned rows, access other columns of the user via your $row variable. Just like you access the user's hashed password with $row->password, you could access $row->contactNumber.
A note about good practice:
Depending on the case, I would not recommend doing a wildcard (*) SELECT command at login. In fact, I would recommend simply hashing the password prior to the query and attempting to then qualify your query with WHERE username = :username AND password = :password (obviously, bind the hashed password to :password). Instead of asking for the password column, or wildcard columns, you could SELECT the row's unique ID.
This way, you don't even need to iterate over the returned rows at all...you only have to make sure any row returned (see num_rows) to see if the user can be successfully "logged in". You can then cache the returned row's unique ID into your session, and then do subsequent queries as necessary for other pieces of information...such as the user's role or contact number. This effectively brings the complexity of all of your query processing down from linear time to constant time...a minor, but still non-trivial, improvement for an application.
Also, as a word of warning, this login system is very simple and easily spoofed. PHP sessions provide some security, but they are not full-proof. A sniffer snagging the session cookie will allow them to log in as the user whom they sniffed it from. I would recommend looking into adding in your own session layer as well once you have the rest of your login system implemented. And absolutely use SSL.
A note about optimization:
Using a wildcard (*) in a SELECT command is actually a prime place for a speed bottleneck to occur. If you know exactly what columns you want from the database, it is best to ask for them explicitly. For example, instead of *, you could do password,occupation,role.
$_SESSION['username'] = $username;
You have user name in session right.
Just pass this value in where condition of mysql .and get the entire record from login table .just show where ever you want to show.
You can change your SELECT statement to return the other values you want to store in your $_SESSION variables and then access them each with $row->{variable}
Just make sure you populate the $_SESSION after you do your password check
Not dissimilar to your existing code - use the session variable you set when the user logs in
$username=!empty( $_SESSION['username'] ) ? $_SESSION['username'] : false;
$sql='select * from `login` where `username`=:username;';
$stmt=$conn->prepare( $sql );
if( $stmt && $username ){
$stmt->bindValue(':username',$username);
$result=$stmt->execute();
if( $result ){
$rs=$stmt->fetchAll( PDO::FETCH_OBJ );
/* display results from $rs */
/*
There should most likely only be one record!!
*/
$row=$rs[0];
foreach( $row as $column => $value )echo $column.' '.$value;
}
}
I am using prepared statements to log in, but I am not able to log in with any password and username pairs. I don't know why. It seems like $result = $stmt->get_result(); never returns false because when I print $result, I get:
result: mysqli_result Object (
[current_field] => 0 [field_count] => 2 [lengths] => [num_rows] => 0 [type] => 0
)
How do I fix this?
Code
if(isset($_POST["Logsub"])){
if(!empty($_POST["username"]) && !empty($_POST["password"])){
$username = filter_var($_POST["username"], FILTER_SANITIZE_STRING);
$password_sanitized = filter_var($_POST["password"], FILTER_SANITIZE_STRING);
$password = hash( "sha256", $password_sanitized ); //"sha256" is a type of hashing algorithm
if(preg_match("/^[a-zA-Z0-9]+$/", $username)&& preg_match("/^[a-zA-Z0-9]+$/", $password)){
$query = "SELECT * FROM login WHERE username = ? AND password = ?";
$stmt = $mysqli->stmt_init();
if ($stmt->prepare($query)) {
$stmt->bind_param('ss', $username, $password );
$stmt->execute();
//Returns a resultset for successful SELECT queries, or FALSE
$result = $stmt->get_result();
//If results (query for username and password that was entered) returns false
if(!$result) {
echo "<div class = 'message'> Invalid username or password. Please try again </div>";
} else {
echo "<div class = 'message'> You Have Been Successfully Logged In. </div>";
}
}
$stmt->close();
}
}
}
Database
`login` (`username`, `password`) VALUES (username, 'someHashedPassword');
get_results returns FALSE when it fails, otherwise it returns the dataset asked in the prepared statement. You are getting 0 rows, meaning that there is no user with such username and password. You are relying on FALSE instead of 0 rows to show that username or password is incorrect, which is wrong. It will only return FALSE if there was any error executing the prepared statement.
If you are concerned about why 0 results are returning and expect correct login, it can happen if the calculated hash of your password is not matching the hash which is stored in database.
Un-filtered approach:
if(isset($_POST["Logsub"]) && !empty($_POST["username"]) && !empty($_POST["password"])){
$username=$_POST["username"];
$password=hash("sha256",$_POST["password"]);
$query="SELECT * FROM login WHERE username=? AND password=?";
if($stmt=$mysqli->prepare($query)){
if($stmt->bind_param('ss',$username,$password) &&
$stmt->execute() &&
$stmt->store_result()
){
if($result=$stmt->get_result()){
echo "<div class=\"message\">You have been successfully logged in.</div>";
}else{
echo "<div class=\"message\">Invalid username or password. Please try again.</div>";
}
}else{
echo "Statement Error: ",$stmt->error; // do not echo when public
}
$stmt->close();
}else{
echo "Prepare Error: ",$mysqli->error; // do not echo when public
}
$mysqli->close();
}else{
echo "Insufficient or invalid credentials submitted";
}
You can combine your two initial condition statements into one.
You are likely damaging the password value while filtering/sanitizing. You can trust your prepared statement to keep your query safe from injection; all of that pre-filtering is unnecessary.
$stmt = $mysqli->stmt_init(); is in the manual but prepared statements will work without it (most people just put the dbh directly on prepare; this is my preference).
I have batched all of the stmt actions into one condition statement. If any of the conditions fail, php will immediately go to else condition and display the error.
I declared $result just in case you want to use it in your actual project.
If this code block doesn't permit login with the correct credentials, please echo out your $query, $username, and hash'ed $password glue it all together and run it directly on your database to ensure that it does/can match a row.
(*I did not test this code before posting)
The basic control structure I'm trying to get to work is to query the DB with the username and email, both of which are unique keys, and if either are in the DB let the user know that they have been taken and to please pick something else. The problem I'm running into is getting the result data in a usable form that I can then check the user-supplied data against.
I cut out the prepared statements for insertion from the snippit, as well as the validation routines, since both of them are working fine.
DB connection snippit
try {
if(!($dbc = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME))){ // Creates the $dbc variable object so we can
// have a connection to the database.
// uses mysqli functions.
throw new Exception;
}
}
catch (Exception $e) {
echo '<p>Could not connect to the database. Please contact the system administrator.</p>';
}
Snippit of Registration script
//before this was validation routines, if anything was wrong the script generated something into $reg_errors which is an array.
if(empty($reg_errors))
{
//queries database if there are any matches for username or email from user input.
if($stmt = $dbc->prepare("SELECT `email`, `username` FROM `users` WHERE `email` = ? OR `username` = ?"))
{
$stmt->bind_param("ss", $e, $u);
$stmt->execute();
$stmt->store_result();
$rows = $stmt->num_rows; //gives the number of rows returned from SELECT query. 0 means no dupes, 1 means one record has BOTH email and username, 2 means two different records (one with email, one with username)
##THIS IS WHERE I'M RUNNING INTO TROUBLE GETTING THE DATA IN A USABLE FORM##
$stmt->close();
} else {
echo "<p>Can't talk to database right now. Try again later, please.</p>";
}
if($rows==0) //no dupes of username or email, so let's try and add them into the DB
{
//prepared statement for insertion into DB
//also get's the count of affected rows. 1 means record inserted correctly.
//asks DB if a new row was created, and if so, thanks user for
//registration on the site & sends an email to their email.
//if query doesnt work, an error is triggered
if($count==1) {
//constructs a thank you note and emails it to the user, using the email they supplied.
exit();
} else {
echo "<p>Unable to process your registration at this time. Please try again later..</p>";
}
} else { // both username and email might be already used in DB, and error msgs are generated for array.
if($rows==2) { // this checks to make sure both entries are dupes
$reg_errors['email'] = 'This email address has already been registered. If you have forgotten your password, use the link to the right to have your password sent to you.';
$reg_errors['username'] = 'This username has already been registered. Please try another.';
} else { //this checks to see which of the two (email or username) is already in DB if both arent dupes.
if((__NEED SOMETHING HERE FROM DB QUERY___ == $_POST['email']) && (__NEED SOMETHING HERE FROM DB QUERY___ == $_POST['username'])) { //both match entries in DB
$reg_errors['email'] = 'This email address has already been registered. If you have forgotten your password, use the link to the right to have your password sent to you.';
$reg_errors['username'] = 'This username has already been registered with this email address. If you have forgotten your password, use the link to the right to have your password sent to you.';
} elseif(__NEED SOMETHING HERE FROM DB QUERY___==$_POST['email']) { // email match
$reg_errors['email'] = 'This email address has already been registered. If you have forgotten your password, use the link to the right to have your password sent to you.';
} elseif(__NEED SOMETHING HERE FROM DB QUERY___==$_POST['username']) { // username match
$reg_errors['username'] = 'This username has already been registered. Please try another one.';
}
} // end of $rows==2 ELSE
} // end of $rows == 0 IF
} else { // end of empty reg_errors conditional
//do something if the reg_error array isnt empty..
}
i'm pretty sure the answer lies in iterations and using meta_data from the result mysqli object, but after beating my head against a wall for a couple days and pouring over the mysqli php manual pages like a maniac, I'm still no closer to figuring out what I should be doing. Could anyone point me in the correct direction?
Starting from the registration script, have you tried this:
if($stmt = $dbc->prepare("SELECT `email`, `username` FROM `users` WHERE `email` = ? OR `username` = ?"))
{
$stmt->bind_param("ss", $e, $u);
$stmt->execute();
$stmt->bind_result($email, $username);
$rows = $stmt->num_rows;
//Move Conditionals Up a Little
if( $rows == 0 ) { //If No Records are Found
//Continue Registration
}
else if( $rows == 1 ) { //If One Record is Found
$stmt->fetch();
//Do Something With $email and $username from DB Here
}
else { //If More than One Record is Found
while( $stmt->fetch() ) { //Iterate Through Records
//Do Something With $email and $username from DB Here
}
}
}