Log In and Registration Form Password - php

I am making a log in and registration form. Since I am dealing with passwords, I want to do it right so excuse the long lines of code. I have manage to do the registration form that will hash the password. But My problem is when logging in the password is not reading it and I am using only one mock account and one password. Do you think its the hashing? Please help
PHP Code(I have made a functions.php file that has the functions needed to do this log in it contains)
login function
function login($email, $password, $mysqli) {
// Using prepared Statements means that SQL injection is not possible.
if ($stmt = $mysqli->prepare("SELECT accountID, UserName, Password, salt FROM accounts WHERE email = ? LIMIT 1")) {
$stmt->bind_param('s', $email); // Bind "$email" to parameter.
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
$stmt->bind_result($user_id, $username, $db_password, $salt); // get variables from result.
$stmt->fetch();
$password = hash('sha512', $password.$salt); // hash the password with the unique salt.
if($stmt->num_rows == 1) { // If the user exists
// We check if the account is locked from too many login attempts
if(checkbrute($user_id, $mysqli) == true) {
// Account is locked
// Send an email to user saying their account is locked
return false;
} else {
if($db_password == $password) { // Check if the password in the database matches the password the user submitted.
// Password is correct!
$user_browser = $_SERVER['HTTP_USER_AGENT']; // Get the user-agent string of the user.
$user_id = preg_replace("/[^0-9]+/", "", $user_id); // XSS protection as we might print this value
$_SESSION['user_id'] = $user_id;
$username = preg_replace("/[^a-zA-Z0-9_\-]+/", "", $username); // XSS protection as we might print this value
$_SESSION['username'] = $username;
$_SESSION['login_string'] = hash('sha512', $password.$user_browser);
// Login successful.
return true;
} else {
// Password is not correct
// We record this attempt in the database
$now = time();
$mysqli->query("INSERT INTO login_attempts (user_id, time) VALUES ('$user_id', '$now')");
return false;
}
}
} else {
// No user exists.
return false;
}
}
}
I have a checkbrute function that deals with forced logins
function checkbrute($user_id, $mysqli) {
// Get timestamp of current time
$now = time();
// All login attempts are counted from the past 2 hours.
$valid_attempts = $now - (2 * 60 * 60);
if ($stmt = $mysqli->prepare("SELECT time FROM login_attempts WHERE user_id = ? AND time > '$valid_attempts'")) {
$stmt->bind_param('i', $user_id);
// Execute the prepared query.
$stmt->execute();
$stmt->store_result();
// If there has been more than 5 failed logins
if($stmt->num_rows > 5) {
return true;
} else {
return false;
}
}
}
Finaly I have a login_check to check if all session variables are set
function login_check($mysqli) {
// Check if all session variables are set
if(isset($_SESSION['user_id'], $_SESSION['username'], $_SESSION['login_string'])) {
$user_id = $_SESSION['user_id'];
$login_string = $_SESSION['login_string'];
$username = $_SESSION['username'];
$user_browser = $_SERVER['HTTP_USER_AGENT']; // Get the user-agent string of the user.
if ($stmt = $mysqli->prepare("SELECT Password FROM accounts WHERE accountID = ? LIMIT 1")) {
$stmt->bind_param('i', $user_id); // Bind "$user_id" to parameter.
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
if($stmt->num_rows == 1) { // If the user exists
$stmt->bind_result($password); // get variables from result.
$stmt->fetch();
$login_check = hash('sha512', $password.$user_browser);
if($login_check == $login_string) {
// Logged In!!!!
return true;
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
} else {
// Not logged in
return false;
}
}
I am running the login form via separate html file
<body>
<form action="process_login.php" method="post" name="login_form">
Email: <input type="text" name="email" value=""/>
Password: <input type="password" name="password" id="password" value="" />
<input type="button" value="Login" onclick="formhash(this.form, this.form.password);" />
</form>
</body>
</html>
and the Process_login.php
include 'db_connect.php';
include 'functions.php';
sec_session_start(); // Our custom secure way of starting a php session.
if(isset($_POST['email'], $_POST['password'])) {
$email = $_POST['email'];
$password = $_POST['password']; // The hashed password.
if(login($email, $password, $mysqli) == true) {
// Login success
echo 'Success: You have been logged in!';
} else {
// Login failed
echo 'Fail';
}
} else {
// The correct POST variables were not sent to this page.
echo 'Invalid Request';
}
Thanks

Absolute ZERO is right, you are using random salts and you are comparing password hashes as a whole with each other, they will never match. I think you should be using bcrypt. In that you can create random and unique salts, but they crypt() function in that only checks for the part that was hashed and not the salts.
This is a more sensible way of doing it and you won't have to create a separate column for storing salts. Also bcrypt is probably the safest method hashing method available today, and it is really slow, which would only be a problem for attackers and not your users.
I don't know a tutorial that shows how to use bcrypt, but I'm sure u'll find tons when u search on Google. However, here is a very good tutorial on a Log in and registration system that also uses bcrypt(): http://www.sunnytuts.com/article/login-and-registration-with-object-oriented-php-and-pdo
I think you'll find it really useful, but make sure you continue to Part 2 of that tutorial.
Good luck.

You've got the right idea with the security, but if you're generating a new random salt on every attempt when you're trying to compare with an existing record it should never match (it's random)*. You have to store the salt in the table along with the salted hashed password. The salt should be random for each user (as in random, not their user agent), but you'll need to use the salt from the creation of the account to hash the user's password input for comparison.
To make the fix:
You'll need to add a 'salt' column to your accounts table (or a relation table that you can relate to the accounts table).
Once that's added then you would need to adjust any of the functions where you're doing an insert into the database with the user's information. You'll need to add salt='$salt' to the SQL.
On the checks for input login_check() and login() you'll need to do the following:
Update the SQL for the query to check the username first:
"select count(*) from accounts where username = '$username'"
If the count is '1', then do this:
"select salt from accounts where username = '$username'"
Then you would do this:
$password_for_comparison = hash('sha512', $password.$salt_from_database);
Then you would do this:
"select count(*) from accounts where username='username' and password='$password_for_comparison'"
If this count is 1 then you have a valid user.
Those few things can be added into a new function and called in the other functions (so you're not maintaining two separate code sets).
*Note: There are cases where a collision may occur, but with your selection of sha512 this is such a random possibility it's unlikely without someone trying brute force the thing with something like hashcat on a box with 25 vid cards running opencl.

Related

How to password protect website and keep user logged in, using PHP?

I want to make my website password protected. After a user logged in he shouldn't get logged out when closing window or browser. To reach this I'm storing an authentication token as cookie.
This is my code:
<?php
// take $_COOKIE['user'] if it exist, else take $_POST['user']
if(isset($_COOKIE['user'])) {
$user = $_COOKIE['user'];
} else {
$user = $_POST['user'];
}
// get login data from database
if(isset($user)) {
$stmt = $pdo->prepare('SELECT user, password, authentication_token FROM users WHERE user = :user LIMIT 1;');
$stmt->execute(array(':user' => $user));
$results = $stmt->fetchAll();
foreach($results as $row) {
$password = $row['password'];
$authentication_token = $row['authentication_token'];
}
}
// set passwort and authentication token at first visit and login
if($password == "" && isset($_POST['user'], $_POST['password'])) {
$unique_authentication_token = hash('sha256', uniqid(rand().time(), true));
$statement = $pdo->prepare("UPDATE users SET password = :password, authentication_token = :authentication_token WHERE user = :user");
$statement->execute(array(':user' => $_POST['user'], ':password' => hash('sha256', $_POST['password']), ':authentication_token' => $unique_authentication_token));
setcookie("user", $_POST['user'], time()+(10*365*24*60*60));
setcookie("authentication_token", $unique_authentication_token, time()+(10*365*24*60*60));
unset($_POST);
header('location: https://www.example.com');
exit;
}
// show login form if no data or wrong data
if(!isset($_COOKIE['user']) || !isset($_COOKIE['authentication_token']) || $_COOKIE['authentication_token'] != $authentication_token) {
$showLogin = 1;
}
// login
if(isset($_POST['user'], $_POST['password']) && hash('sha256', $_POST['password']) == $password) {
setcookie("user", $_POST['user'], time()+(10*365*24*60*60));
setcookie("authentication_token", $authentication_token, time()+(10*365*24*60*60));
unset($_POST);
header('location: https://www.example.com');
exit;
}
// login form
if($showLogin == 1) {
echo "
<form action=\"\" method=\"post\">
<select name=\"user\">
<option>George</option>
<option>Harald</option>
<option>Peter</option>
</select>
<input type=\"password\" name=\"password\" placeholder=\"Password\">
</form>
";
exit;
}
?>
Is it the right way I'm doing it? Can you see any vulnerables? What can I do better?
Comments by Sakezz are very confused.
Yes using a salted hash is hugely better than an unsalted one, but SHA is a much more secure hash than MD5. But php comes with a password_hash() function (and a whole chapter in the manual) which does all the clever stuff for you, allows use of better hashes and simplifies migration to new hash mechanisms.
You are using an authentication token which can be revoked and is effectively random. That's good - although a user can't be logged into more than one device at a time. Do you provide a mechanism for users to logout? (This is important).
You are using parameter binding for your queries, again good, but an even better approach for security would be using a stored procedure with privilege desperation - and the account you connect with should have no read/write access to the table.
The coding style is ok, but unless you have a reason for sticking with it, I suggest psr-1 & 2

MySQL Query is fine, doesn't give a value or an error

I am trying to find how to check if a variable called active is equal to 1. My attempt at the function is below:
function login($email, $password, $mysqli) {
// Using prepared statements means that SQL injection is not possible.
if ($stmt = $mysqli->prepare("SELECT id, username, password, salt, active
FROM members
WHERE email = ? LIMIT 1")) {
$stmt->bind_param('s', $email); // Bind "$email" to parameter.
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
// get variables from result.
$stmt->bind_result($user_id, $username, $db_password, $salt, $active);
$stmt->fetch();
// hash the password with the unique salt.
$password = hash('sha512', $password . $salt);
if ($stmt->num_rows == 1) {
// If the user exists we check if the account is locked
// from too many login attempts
if (checkbrute($user_id, $mysqli) == true) {
// Account is locked
// Send an email to user saying their account is locked
return false;
} else {
// Check if the password in the database matches
// the password the user submitted.
if ($db_password == $password) {
// Password is correct!
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
// XSS protection as we might print this value
$user_id = preg_replace("/[^0-9]+/", "", $user_id);
$_SESSION['user_id'] = $user_id;
// XSS protection as we might print this value
$username = preg_replace("/[^a-zA-Z0-9_\-]+/", "", $username);
$_SESSION['username'] = $username;
$_SESSION['login_string'] = hash('sha512', $password . $user_browser);
} else {
// Password is not correct
// We record this attempt in the database
$now = time();
if (!$mysqli->query("INSERT INTO login_attempts(user_id, time)
VALUES ('$user_id', '$now')")) {
header("Location: ../error?err=Database error: login_attempts");
exit();
}
return false;
}
}
} else {
// No user exists.
return false;
}
} else {
// Could not create a prepared statement
header("Location: ../error?err=Database error: cannot prepare statement");
exit();
}
}
I assume that where I added active to the $mysqli->prepare statement is correct.
What I want to do is if the user got their password correct I would query the MySQL table to see if his account is active(1) or not active(0). If it is set to 0 it logs in with no error. However in my process_login.php file it logs the user in if it is (0) but with index.php?err=1
<?php
include_once 'db_connect.php';
include_once 'functions.php';
sec_session_start(); // Our custom secure way of starting a PHP session.
if (isset($_POST['email'], $_POST['p'])) {
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$password = $_POST['p']; // The hashed password.
if (login($email, $password, $mysqli) == true) {
// Login success
header("Location: ../protected_page.php");
exit();
} else {
// Login failed
header('Location: ../index.php?error=1');
echo $active;
exit();
}
} else {
// The correct POST variables were not sent to this page.
header('Location: ../error.php?err=Could not process login');
exit();
}
When I try to echo the variable $active it returns nothing.
Any help is appreciated in advance.
Posting this as a community wiki; I don't want rep for it, nor should there be any made from it.
A: You did not follow that tutorial exactly as it was written.
http://www.wikihow.com/Create-a-Secure-Login-Script-in-PHP-and-MySQL
since it is obvious that that is where that code comes from; I know it all too well.
You modified some parts of the code and left some out also.
Go back to the tutorial, and follow it " to a T ". You may also have to clear out your present hashes and start over.
Make sure that the table creation was done exactly as shown. If you failed to make the right columns and their proper lengths, then that will fail "silently" on you.
Consult the comments I left under the question also.

Using MD5 and Password hash function when storing in database

If I have this PHP script for a login system:
$user = $_POST['user_name'];
$pass = md5($_POST['user_pass']);
require_once("connection_file.php");
$sql = "SELECT * FROM login_table WHERE user_n = :us AND user_p = :password";
$stmt = $conn->prepare($sql);
$stmt->bindValue(':us', $user, PDO::PARAM_STR);
$stmt->bindValue(':password', $pass, PDO::PARAM_STR);
$stmt->execute();
$result = $stmt->fetchAll();
if($result)
{
//echo $msg = "user exist";
if(session_status() == PHP_SESSION_NONE)
{
session_start();
foreach($result as $row)
{
$hash = password_hash($row['user_pass'], PASSWORD_BCRYPT);
if(password_verify($row['user_pass'], $hash))
{
$_SESSION['userid'] = $row['user_id'];
$_SESSION['role'] = $row['user_role'];
header("Location: homepage.php");
}
}
}
}
else
{
$msg = "Wrong credentials";
header("Location: login_page.php");
}
And as you see I am already saving my password in database as MD5 and I am using $pass = md5($_POST['user_pass']); to verify if the text input by the user is equal to MD5 hash.
Now my question is should I use the password_hash and password_verify as I am using in this script ? Or using MD5 would be enough ?
And my second question is can I save passwords in database using the hash string result or it is okay to use the md5 one?
Yes, you should migrate to the new API and never use MD5 for this purpose again, immediately.
If you're not using password_hash()/password_verify() and want to migrate your code to a more secure method, seamlessly:
Add a column to your user accounts table, called legacy_password (or equivalent).
Calculate the bcrypt hash of the existing MD5 hashes and store them in the database (setting legacy_password to TRUE).
Modify your authentication code to handle the legacy flag.
When a user attempts to login, first check if the legacy_password flag is set. If it is, first pre-hash their password with MD5, then use this prehashed value in place of their password. Afterwards, recalculate the bcrypt hash and store the new hash in the database, disabling the legacy_password flag in the process. A very loose example in PHP 7+ follows:
/**
* This is example code. Please feel free to use it for reference but don't just copy/paste it.
*
* #param string $username Unsafe user-supplied data: The username
* #param string $password Unsafe user-supplied data: The password
* #return int The primary key for that user account
* #throws InvalidUserCredentialsException
*/
public function authenticate(string $username, string $password): int
{
// Database lookup
$stmt = $this->db->prepare("SELECT userid, passwordhash, legacy_password FROM user_accounts WHERE username = ?");
$stmt->execute([$username]);
$stored = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$stored) {
// No such user, throw an exception
throw new InvalidUserCredentialsException();
}
if ($stored['legacy_password']) {
// This is the legacy password upgrade code
if (password_verify(md5($password), $stored['passwordhash'])) {
$newhash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $this->db->prepare("UPDATE user_accounts SET passwordhash = ?, legacy_password = FALSE WHERE userid = ?");
$stmt->execute([$newhash, $stored['userid']]);
// Return the user ID (integer)
return $stored['userid'];
}
} elseif (password_verify($password, $stored['passwordhash'])) {
// This is the general purpose upgrade code e.g. if a future version of PHP upgrades to Argon2
if (password_needs_rehash($stored['passwordhash'], PASSWORD_DEFAULT)) {
$newhash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $this->db->prepare("UPDATE user_accounts SET passwordhash = ? WHERE userid = ?");
$stmt->execute([$newhash, $stored['userid']]);
}
// Return the user ID (integer)
return $stored['userid'];
}
// When all else fails, throw an exception
throw new InvalidUserCredentialsException();
}
Usage:
try {
$userid = $this->authenticate($username, $password);
// Update the session state
// Redirect to the post-authentication landing page
} catch (InvalidUserCredentialsException $e) {
// Log the failure
// Redirect to the login form
}
Proactively upgrading legacy hashes is a security win over an opportunistic strategy (rehashing when the user logs in, but leave the insecure hashes in the database for inactive users): With a proactive strategy, if your server gets compromised before everyone logs in again, their passwords are already using an acceptable algorithm (bcrypt, in the example code).
The above example code is also available in Bcrypt-SHA-384 flavor.
Also, this has nothing to do with encryption.

PHP/MySQL Login System not checking password

I'm trying to follow along with a tutorial for creating a login system for a website but some of the code isn't running. Signing up works fine and I can see the entries in my database, but logging in fails even if I have the correct details. Here is my login.php code:
<?php
// First we execute our common code to connection to the database and start the session
require("common.php");
// This variable will be used to re-display the user's username to them in the
// login form if they fail to enter the correct password. It is initialized here
// to an empty value, which will be shown if the user has not submitted the form.
$submitted_username = '';
// This if statement checks to determine whether the login form has been submitted
// If it has, then the login code is run, otherwise the form is displayed
if(!empty($_POST))
{
// This query retrieves the user's information from the database using
// their username.
$query = "SELECT id, username, password, salt, email FROM users WHERE username = :username";
// The parameter values
$query_params = array(
':username' => $_POST['username']
);
try
{
// Execute the query against the database
$stmt = $db->prepare($query);
$result = $stmt->execute($query_params);
}
catch(PDOException $ex)
{
// Note: On a production website, you should not output $ex->getMessage().
// It may provide an attacker with helpful information about your code.
die("Failed to run query: " . $ex->getMessage());
}
// This variable tells us whether the user has successfully logged in or not.
// We initialize it to false, assuming they have not.
// If we determine that they have entered the right details, then we switch it to true.
$login_ok = false;
// Retrieve the user data from the database. If $row is false, then the username
// they entered is not registered.
$row = $stmt->fetch();
if($row)
{
// Using the password submitted by the user and the salt stored in the database,
// we now check to see whether the passwords match by hashing the submitted password
// and comparing it to the hashed version already stored in the database.
$check_password = hash('sha256', $_POST['password'] . $row['salt']);
for($round = 0; $round < 65536; $round++)
{
print("Checking password");
$check_password = hash('sha256', $check_password . $row['salt']);
}
if($check_password === $row['password'])
{
// If they do, then we flip this to true
$login_ok = true;
}
}
// If the user logged in successfully, then we send them to the private members-only page
// Otherwise, we display a login failed message and show the login form again
if($login_ok)
{
// Here I am preparing to store the $row array into the $_SESSION by
// removing the salt and password values from it. Although $_SESSION is
// stored on the server-side, there is no reason to store sensitive values
// in it unless you have to. Thus, it is best practice to remove these
// sensitive values first.
unset($row['salt']);
unset($row['password']);
// This stores the user's data into the session at the index 'user'.
// We will check this index on the private members-only page to determine whether
// or not the user is logged in. We can also use it to retrieve
// the user's details.
$_SESSION['user'] = $row;
// Redirect the user to the private members-only page.
header("Location: secret.html");
die("Redirecting to: secret.html");
}
else
{
// Tell the user they failed
//print("Login Failed.");
// Show them their username again so all they have to do is enter a new
// password. The use of htmlentities prevents XSS attacks. You should
// always use htmlentities on user submitted values before displaying them
// to any users (including the user that submitted them). For more information:
// http://en.wikipedia.org/wiki/XSS_attack
$submitted_username = htmlentities($_POST['username'], ENT_QUOTES, 'UTF-8');
}
}
?>
<h1>Login</h1>
<form action="login.php" method="post">
Username:<br />
<input type="text" name="username" value="<?php echo $submitted_username; ?>" />
<br /><br />
Password:<br />
<input type="password" name="password" value="" />
<br /><br />
<input type="submit" value="Login" />
</form>
Register
Here is my register.php code:
<?php
// First we execute our common code to connection to the database and start the session
require("common.php");
// This if statement checks to determine whether the registration form has been submitted
// If it has, then the registration code is run, otherwise the form is displayed
if(!empty($_POST))
{
// Ensure that the user has entered a non-empty username
if(empty($_POST['username']))
{
// Note that die() is generally a terrible way of handling user errors
// like this. It is much better to display the error with the form
// and allow the user to correct their mistake. However, that is an
// exercise for you to implement yourself.
die("Please enter a username.");
}
// Ensure that the user has entered a non-empty password
if(empty($_POST['password']))
{
die("Please enter a password.");
}
// Make sure the user entered a valid E-Mail address
// filter_var is a useful PHP function for validating form input, see:
// http://us.php.net/manual/en/function.filter-var.php
// http://us.php.net/manual/en/filter.filters.php
if(!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL))
{
die("Invalid E-Mail Address");
}
// We will use this SQL query to see whether the username entered by the
// user is already in use. A SELECT query is used to retrieve data from the database.
// :username is a special token, we will substitute a real value in its place when
// we execute the query.
$query = "
SELECT
1
FROM users
WHERE
username = :username
";
// This contains the definitions for any special tokens that we place in
// our SQL query. In this case, we are defining a value for the token
// :username. It is possible to insert $_POST['username'] directly into
// your $query string; however doing so is very insecure and opens your
// code up to SQL injection exploits. Using tokens prevents this.
// For more information on SQL injections, see Wikipedia:
// http://en.wikipedia.org/wiki/SQL_Injection
$query_params = array(
':username' => $_POST['username']
);
try
{
// These two statements run the query against your database table.
$stmt = $db->prepare($query);
$result = $stmt->execute($query_params);
}
catch(PDOException $ex)
{
// Note: On a production website, you should not output $ex->getMessage().
// It may provide an attacker with helpful information about your code.
die("Failed to run query: " . $ex->getMessage());
}
// The fetch() method returns an array representing the "next" row from
// the selected results, or false if there are no more rows to fetch.
$row = $stmt->fetch();
// If a row was returned, then we know a matching username was found in
// the database already and we should not allow the user to continue.
if($row)
{
die("This username is already in use");
}
// Now we perform the same type of check for the email address, in order
// to ensure that it is unique.
$query = "
SELECT
1
FROM users
WHERE
email = :email
";
$query_params = array(
':email' => $_POST['email']
);
try
{
$stmt = $db->prepare($query);
$result = $stmt->execute($query_params);
}
catch(PDOException $ex)
{
die("Failed to run query: " . $ex->getMessage());
}
$row = $stmt->fetch();
if($row)
{
die("This email address is already registered");
}
// An INSERT query is used to add new rows to a database table.
// Again, we are using special tokens (technically called parameters) to
// protect against SQL injection attacks.
$query = "
INSERT INTO users (
username,
password,
salt,
email
) VALUES (
:username,
:password,
:salt,
:email
)
";
// A salt is randomly generated here to protect again brute force attacks
// and rainbow table attacks. The following statement generates a hex
// representation of an 8 byte salt. Representing this in hex provides
// no additional security, but makes it easier for humans to read.
// For more information:
// http://en.wikipedia.org/wiki/Salt_%28cryptography%29
// http://en.wikipedia.org/wiki/Brute-force_attack
// http://en.wikipedia.org/wiki/Rainbow_table
$salt = dechex(mt_rand(0, 2147483647)) . dechex(mt_rand(0, 2147483647));
// This hashes the password with the salt so that it can be stored securely
// in your database. The output of this next statement is a 64 byte hex
// string representing the 32 byte sha256 hash of the password. The original
// password cannot be recovered from the hash. For more information:
// http://en.wikipedia.org/wiki/Cryptographic_hash_function
$password = hash('sha256', $_POST['password'] . $salt);
// Next we hash the hash value 65536 more times. The purpose of this is to
// protect against brute force attacks. Now an attacker must compute the hash 65537
// times for each guess they make against a password, whereas if the password
// were hashed only once the attacker would have been able to make 65537 different
// guesses in the same amount of time instead of only one.
for($round = 0; $round < 65536; $round++)
{
$password = hash('sha256', $password . $salt);
}
// Here we prepare our tokens for insertion into the SQL query. We do not
// store the original password; only the hashed version of it. We do store
// the salt (in its plaintext form; this is not a security risk).
$query_params = array(
':username' => $_POST['username'],
':password' => $password,
':salt' => $salt,
':email' => $_POST['email']
);
try
{
// Execute the query to create the user
$stmt = $db->prepare($query);
$result = $stmt->execute($query_params);
}
catch(PDOException $ex)
{
// Note: On a production website, you should not output $ex->getMessage().
// It may provide an attacker with helpful information about your code.
die("Failed to run query: " . $ex->getMessage());
}
// This redirects the user back to the login page after they register
header("Location: login.php");
// Calling die or exit after performing a redirect using the header function
// is critical. The rest of your PHP script will continue to execute and
// will be sent to the user if you do not die or exit.
die("Redirecting to login.php");
}
?>
<h1>Register</h1>
<form action="signup.php" method="post">
Username:<br />
<input type="text" name="username" value="" />
<br /><br />
E-Mail:<br />
<input type="text" name="email" value="" />
<br /><br />
Password:<br />
<input type="password" name="password" value="" />
<br /><br />
<input type="submit" value="Register" />
</form>
I have managed to narrow the problem down to:
$row = $stmt->fetch();
if($row)
{
// Using the password submitted by the user and the salt stored in the database,
// we now check to see whether the passwords match by hashing the submitted password
// and comparing it to the hashed version already stored in the database.
$check_password = hash('sha256', $_POST['password'] . $row['salt']);
for($round = 0; $round < 65536; $round++)
{
print("Checking password");
$check_password = hash('sha256', $check_password . $row['salt']);
}
if($check_password === $row['password'])
{
// If they do, then we flip this to true
$login_ok = true;
}
}
as the "print("checking password");" never happens. Can anyone spot a problem in the code? Thank you in advance for your help.
I realised what my problem was... I was attempting to login with the email instead of the username! Thank you all for your help.

XenForo Login Authentication

I'm working on a 3rd party application, and I need to make a stand alone login authentication. I don't want to setup a session or create any local data vars of the sort, I just want to check the username/password for a match. The issue standing is that the XF authentication system doesn't make much sense to me. I've looked around the official XF website, but I haven't gathered much. I understand that password/salt data is stored in the xf_user_authentication table, but I don't know what to make of it. Would anyone be so kind as to explain to me how passwords are formed and what does what within this table?
Posting this answer for anyone who comes across this in the future.
XenForo stores the users passwords in xf_user_authenticate, but without usernames, only user_id to determine the user. The usernames are stored in xf_user. In order to compare the username & password, you'll need to SELECT user_id FROM xf_user WHERE username LIKE ... and using that you'll need to SELECT data FROM xf_user_authenticate WHERE user_id LIKE ..., then simply use password_verify(); to compare the sanitized user input to the hashed password stored in the database. Do note that the hashed passwords are store in a .bin blob along with the cost and salt values used to hash them, these are default values of password_verify();, so all you'll really need to do prior to comparison is to strip the rest of the characters away from the hashed password itself using $password = substr($password, 22, -3); ($password being the string from the database) as the hashed password starts after 22 characters in with 3 trailing characters at the end, the hash being 60 characters long with the total blob character count being 85.
I as well was tasked recently with developing a login verification system that would work with the user accounts that were created on the register page on the forum. This way you can attach those scripts with the correct database connection info to any external website, even on a different server (assuming you're not using shared hosting) and successfully verify a users login request against the forum account information. From there you'll have no problems pulling things like profile information or the accounts avatars, or even their post history!
Here's what I've come up with:
First up, the obvious. Setup your database connection:
<?
define('DB_SERVER', 'localhost');
define('DB_USER', 'phpMyAdmin Login Here');
define('DB_PASS', 'phpMyAdmin Password Here');
define('DB_DATABASE', 'Typically [user_xenpubdatabase]');
$conn = mysqli_connect(DB_SERVER, DB_USER, DB_PASS, DB_DATABASE);
if (!$conn) {
die('Database connection failed: ' . mysqli_connect_error());
?>
Now I've also opted to use functions and store those separate from the login form, so when a user submits, the functions.php is included on login.
Here's my functions:
<?php
function sanitize($input, $conn){
$input = trim($input); // removing spaces
$strlenght = strlen($input);//get the length of the input
for($i = 0; $i <= $strlenght; $i++) {//foreach sting char
$input = stripslashes($input); // remove backslashes
}
$input = htmlspecialchars($input); // make sure code gets declared
$input = mysqli_real_escape_string($conn, $input); //disable code execution
return $input;
}
function checkForExistingAccount($username, $conn){
if ($stmt = mysqli_prepare($conn, "SELECT id from users where username = ?")) {
mysqli_stmt_bind_param($stmt, 's', $username);
mysqli_stmt_execute($stmt);
mysqli_stmt_store_result($stmt);
if (mysqli_stmt_num_rows($stmt) > 0){//if an account returned then rip the username already exists...
mysqli_stmt_close($stmt);
return true;//so return true
}
mysqli_stmt_close($stmt);
}
return false;
}
function checkUsername($username, $conn){
if ($stmt2 = mysqli_prepare($conn, "SELECT user_id FROM xf_user WHERE username = ?")){
mysqli_stmt_bind_param($stmt2, "s", $username);
mysqli_stmt_execute($stmt2);
mysqli_stmt_store_result($stmt2);
if (mysqli_stmt_num_rows($stmt2) > 0){
mysqli_stmt_close($stmt2);
return true;
}
}
return false;
}
function getPassword($username, $conn){
if ($stmt2 = mysqli_prepare($conn, "SELECT data FROM xf_user_authenticate WHERE username = ?")){
mysqli_stmt_bind_param($stmt2, "s", $username);
mysqli_stmt_execute($stmt2);
mysqli_stmt_bind_result($stmt2, $password);
mysqli_stmt_fetch($stmt2);
mysqli_stmt_close($stmt2);
$password = substr($password, 22, -3);
return $password;
}
}
function checkPassword($user_pass, $hashed_pass){
if (password_verify($user_pass, $hashed_pass)) {
return true;
}
return false;
}
From there, my login script looks as follows:
if (isset($_POST['login'])){
include 'dbs.php';
include 'functions.php';
if (!empty($username = sanitize($_POST['username'], $conn)) && !empty($password = sanitize($_POST['password'], $conn))) {
if (checkUsername($username, $conn)) {
if (!empty($dbs_password = getPassword($username, $conn))) {
if (checkPassword($password, $dbs_password)) {
session_start();
$_SESSION['active'] = 1;//never use true, some browsers are buggy with that.
mysqli_close($conn);//close conn then redirect
header('location: ../index.php');
} else {
echo '<div class="danger">Sorry, you have entered wrong information</div>';
}
}
} else {
echo '<div class="danger">Sorry, no user found with the username: ' . $username . '</div>';
}
}
else{
echo '<div class="danger">You have to fill in all fields*</div>';
}
mysqli_close($conn);
}

Categories