So I am dealing with merging in functionality for rehashing to upgrade users to have bcrypt passwords, into a existing class I found and have set up quite successfully, its wonderful.
However, this class lacks rehashing check, which is terrible for legacy passwords on existing user databases. We need to handle SHA1 passwords! We use SHA1 + Salt, so I hope this is possible to convert.
Im using this class found here:
https://alexwebdevelop.com/user-authentication/
So using this class, I have added the following public function:
public function authenticate($username, $password)
{
/* Global $pdo object */
global $pdo;
// Database lookup
$stmt = $pdo->prepare("SELECT id, password, legacy_password FROM users WHERE username = ?");
$stmt->execute([$username]);
$stored = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$stored) {
// No such user, throw an exception
throw new Exception('Invalid user.');
}
if ($stored['legacy_password']) {
// This is the legacy password upgrade code
if (password_verify(sha1($password), $stored['password'])) {
$newHash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("UPDATE users SET password = ?, legacy_password = FALSE WHERE id = ?");
$stmt->execute([$newhash, $stored['id']]);
// Return the user ID (integer)
return $stored['id'];
}
} elseif (password_verify($password, $stored['password'])) {
// This is the general purpose upgrade code e.g. if a future version of PHP upgrades to Argon2
if (password_needs_rehash($stored['password'], PASSWORD_DEFAULT)) {
$newhash = password_hash($password, PASSWORD_BCRYPT);
$stmt = $pdo->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->execute([$newhash, $stored['id']]);
}
// Return the user ID (integer)
return $stored['id'];
}
// When all else fails, throw an exception
throw new Exception('Rehashing failed.');
}
Now inside the login() function of the class, I have replaced
public function login($name, $passwd)
{
...
if (is_array($row)) {
if (password_verify($passwd, $row['password'])) {
/* Authentication succeeded. Set the class properties (id and name) */
$this->id = intval($row['id'], 10);
$this->name = $name;
$this->authenticated = TRUE;
/* Register the current Sessions on the database */
$this->registerLoginSession();
/* Finally, Return TRUE */
return TRUE;
}
}
}
With this:
public function login($name, $passwd)
{
...
if (is_array($row)) {
$userid = $this->authenticate($name, $row['password']);
if (password_verify($passwd, $row['password'])) {
/* Authentication succeeded. Set the class properties (id and name) */
$this->id = intval($userid);
$this->name = $name;
$this->authenticated = TRUE;
/* Register the current Sessions on the database */
$this->registerLoginSession();
/* Finally, Return TRUE */
return TRUE;
}
}
}
And so it is supposed to return the hand back the ID after check / rehashing. So it finds me as a user, as tested. Good.. so now all authenticate() does is throw exception error of failure. I can't figure out how to get error messages out of this.
This seems like this the exact thing to do with this ID, what am I doing wrong?
This point of this: User logs in with SHA1 (salted) password in form, script rehashes password, and user logs in like nothing happened.
authenticate() conversion function I'm using:
https://paragonie.com/blog/2016/02/how-safely-store-password-in-2016#legacy-hashes
Im so sorry! I have learned from the suggestions here and I appreciate all the help!
So I solved this myself. What I did was remove authenticate() function, and instead tackled this directly based on the feedback comments (I couldn't agree more).
I replaced the last code block in the post, with this:
if (is_array($row)) {
if (password_needs_rehash($row['password'], PASSWORD_DEFAULT)) {
$newhash = password_hash($passwd, PASSWORD_BCRYPT);
$stmt = $pdo->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->execute([$newhash, $row['id']]);
}
if (password_verify($passwd, $row['password'])) {
/* Authentication succeeded. Set the class properties (id and name) */
$this->id = intval($row['id'], 10);
$this->name = $name;
$this->authenticated = TRUE;
/* Register the current Sessions on the database */
$this->registerLoginSession();
/* Finally, Return TRUE */
return TRUE;
}
}
And users passwords are rehashing, and logging in!
Related
I'm writing an MVC style application using OO PHP and have run into an issue when trying to use different classes when trying to register/login users. Essentially, I have an abstract User class holding some common properties and functions and 2 classes which extend this: a LoginUser class created when a user attempts to login and a RegisterUser class created when a user attempts to register.
My issue is this: When I successfully add a user to my database using a query that is called in the RegisterUser class (using the password_hash function on the password) and then try to login via a query called in the LoginUser class (using the password_verify function) the query result returns false, even when the password supplied is definitely the password that was entered at registration.
My question is this: Does the password_verify function have to be called by an object of the same class that used the password_hash function to create the hash? If so, why? I have tried looking at the PHP documentation and search results do not return an answer either!
The reason that I ask this is because the registration/login will succeed if all of the functions are held in a single User class, instead of inherited classes.
My User class:
abstract class User {
protected $checkedUserName = '';
protected $checkedPassword = '';
public function __construct($uncheckedUserName, $uncheckedPassword) {
$this->checkedUserName = $this->validateAndSanitizeUserName($uncheckedUserName);
$this->checkedPassword = $this->validateAndSanitizePassword($uncheckedPassword);
}
protected function validateAndSanitizeUserName($uncheckedUserName) {
$string = filter_var($uncheckedUserName, FILTER_VALIDATE_EMAIL); // Checks input is an email
$string = filter_var($string, FILTER_SANITIZE_EMAIL); // Removes illegal chars
$string = filter_var($string, FILTER_SANITIZE_SPECIAL_CHARS); // Removes HTML tags, etc replacing them with char codes
return $string;
}
protected function validateAndSanitizePassword($uncheckedPassword) {
$string = filter_var($uncheckedPassword, FILTER_VALIDATE_REGEXP, ["options"=>["regexp"=>"/(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/"]]); // Checks the password against the regex on the form
$string = filter_var($string, FILTER_SANITIZE_SPECIAL_CHARS); // Removes HTML tags, etc replacing them with char codes
return $string;
}
protected function checkIfUserExists() {
// Set the initial status of user exists
$userExists = false;
// Open a connection to the database
$con = Db::getInstance();
// Prepare the query
$checkIfUserExists = $con->prepare("SELECT * FROM users2 WHERE username=?");
//Execute the query with the checked username
$checkIfUserExists->execute([$this->checkedUserName]);
// Set $userExists dependent on result
if($checkIfUserExists->rowCount() !== 0) {
$userExists = true;
}
return $userExists;
}
}
My LoginUser class:
class LoginUser extends User{
public function __construct($uncheckedUserName, $uncheckedPassword) {
parent::__construct($uncheckedUserName, $uncheckedPassword);
}
private function getPasswordHashes() {
// Only connect to the database when connection is needed
$con = Db::getInstance();
// Check if username and password match
// Prepare the query
$checkUser = $con->prepare("SELECT * from users2 WHERE username = ?");
// Execute the query using an array to bind the parameter to ?
$checkUser->execute([$this->checkedUserName]);
return $checkUser;
}
public function getLogInResult() {
// Initialise the results variable
$resultsFound = 0;
// Only proceed if the username actually exists
if($this->checkIfUserExists()) {
// Call the function to get the records that match the username
$checkUser = $this->getPasswordHashes();
// Check to see if exactly one match was found and verify the password
if($checkUser->rowCount() === 1) { // Note this may not work in other databases - it does in MySQL
foreach($checkUser as $user) {
if(password_verify($this->checkedPassword, $user['passwordHash'])) {
$resultsFound++;
}
}
}
return $resultsFound;
}
}
}
My RegisterUser class:
lass RegisterUser extends User{
private $checkedFirstName = '';
private $checkedLastName = '';
public function __construct($uncheckedUserName, $uncheckedPassword, $uncheckedFirstName, $uncheckedLastName) {
parent::__construct($uncheckedUserName, $uncheckedLastName);
$this->checkedFirstName = $this->sanitizeString($uncheckedFirstName);
$this->checkedLastName = $this->sanitizeString($uncheckedLastName);
}
private function sanitizeString($uncheckedString) {
$string = filter_var($uncheckedString, FILTER_SANITIZE_STRING);
return $string;
}
private function insertUserDetails() {
// Hash the supplied password in preparation for insertion
//$hashedPassword = password_hash($this->checkedPassword, PASSWORD_DEFAULT);
// Connect to the database
$con = Db::getInstance();
// Prepare the query
$addUser = $con->prepare("INSERT INTO users2 VALUES (?, ?, ?, ?)");
// Execute the query using an array to bind the parameters
$addUser->execute([$this->checkedUserName, password_hash($this->checkedPassword, PASSWORD_DEFAULT), $this->checkedFirstName, $this->checkedLastName]);
// Return the result
return $addUser;
}
public function getRegisterResult() {
// Initialise the variable to store the result state
$result = false;
// Only proceed if the username does not exist
if(!($this->checkIfUserExists())) {
$addUser = $this->insertUserDetails();
// If the details were successfully added
if($addUser->rowCount() === 1) {
$result = true;
}
}
return $result;
}
}
So, when completing the registration form, the getRegisterResult() function is called on a new RegisterUser object. When logging in, the getLoginResult() function is called on a new LoginUser object but the result returns false...
In answer to my question, it doesn't matter which classes use password_hash and password_verify, if there's a match with the password to verify and the hash from the database it should return a positive result!
The issue was with __construct() for the RegisterUser class - the call to the parent passed in $uncheckedLastName rather than the $uncheckedPassword and therefore the password being set at registration was not what was supplied in the password field but that what was supplied in the LastName field!
Here is my Code which I need help with
I need to link user_id to user_location in second table, which I wrote out in the bottom of this question
<?php
/**
* Class UserInfo
* handles the insert information about a paricular User
*/
class UserInfo {
private $db_connection = null;
public $errors = array();
public $messages = array();
public function __construct() {
if(isset($_POST["info"])) {
$this->Insert();
}
}
/**
* Handles the Insertion of user Information into database
**/
private function Insert() {
if (empty($_POST['user_location'])) {
$this->errors[] = "Please fillout a location.";
} elseif (!empty($_POST['user_location'])) {
$this->db_connection = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if (!$this->db_connection->connect_errno) {
$user_id = $_SESSION['user_name'];
$user_location = $this->db_connection->real_escape_string($_POST['user_location']);
$insert = "insert into users_info (user_location) values ('".$user_location."');";
$check_insert = $this->db_connection->query($insert);
if ($check_insert) {
$this->messages[] = "Location inserted into database successfully!";
} else {
$this->errors[] = "Location could not be inserted into database.";
}
}
}
}
}
?>
And here is my Login script
<?php
/**
* Class Login
* handles the user's login and logout process
*/
class Login {
/**
* #var object The database connection
*/
private $db_connection = null;
/**
* #var object Collection of error messages.
*/
public $errors = array();
/**
* #var Collection of success / neutral messages
*/
public $messages = array();
/**
* The function "__construct()" automatically starts whenever an object of this class is created,
* you know, when you do $login = new Login();"
*/
public function __construct() {
// create/read session
session_start();
// check the possible login actions:
// if the user tried to logout ( happens when user clicks logout button )
if (isset($_GET["logout"])) {
$this->doLogout();
}
// login via post data ( if the user just submitted a login form )
elseif (isset($_POST["login"])) {
$this->doLoginWithPostData();
}
} // end function __construct();
/**
* log in with post data
*/
private function doLoginWithPostData() {
// check login form contents
if (empty($_POST['user_name'])) {
$this->errors[] = "Username field is empty.";
} elseif (empty($_POST['user_password'])) {
$this->errors[] = "Password field was empty.";
} elseif (!empty($_POST['user_name']) && !empty($_POST['user_password'])) {
// create a database connection, using the constants from config/db.php
// which we loaded in index.php
$this->db_connection = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
// change character set to utf8 and check it
if(!$this->db_connection->set_charset("utf8")) {
$this->errors[] = $this->db_connection->error;
}
// if no connection errors (working database connection)
if (!$this->db_connection->connect_errno) {
// escape the POST stuff
$user_name = $this->db_connection->real_escape_string($_POST['user_name']);
// database query, getting all the info of the selected user ( allows via email address
// in the username field. )
$sql = "select user_name, user_email, user_password_hash
from users
where user_name = '".$user_name."' or user_email = '".$user_name."';";
$result_of_login_check = $this->db_connection->query($sql);
// if this user exists
if ($result_of_login_check->num_rows == 1) {
// get result row (as an object)
$result_row = $result_of_login_check->fetch_object();
// using PHP 5.5's password_verify() function to check if the provided password fits
// the hash of that user's password
if (password_verify($_POST['user_password'], $result_row->user_password_hash)) {
// write user data into PHP SESSION (a file on your server)
$_SESSION['user_name'] = $result_row->user_name;
$_SESSION['user_email'] = $result_row->user_email;
$_SESSION['user_login_status'] = 1;
} else {
$this->errors[] = "Wrong password. Try again.";
}
} else {
$this->errors[] = "This user does not exist.";
}
} else {
$this->errors[] = "Database connection problem.";
}
}
}
/*
* Preform the logout
*/
public function doLogout() {
// delete the session of the user
$_SESSION = array();
session_destroy();
// return a little feeback message
$this->messages[] = "You have been logged out.";
}
/**
* simply return the current state of the user's login
* #return boolean user's login status
*/
public function isUserLoggedIn() {
if (isset($_SESSION['user_login_status']) AND $_SESSION['user_login_status'] == 1) {
return true;
}
// default return
return false;
}
} // ------> Close class Login
?>
Here is my Database (mySQL)
CREATE TABLE IF NOT EXISTS users (
user_id int(11) NOT NULL AUTO_INCREMENT,
user_name varchar(64) NOT NULL,
user_password_hash varchar(255) NOT NULL,
user_email varchar(64) NOT NULL,
PRIMARY KEY (user_id),
UNIQUE KEY user_name (user_name),
UNIQUE KEY user_email (user_email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS users_info (
user_id int(11) NOT NULL,
user_location varchar(255) NOT NULL,
PRIMARY KEY (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Forign constrants = login2, users, user_id (sorry cant post image yet, need 10 reputation)
Which kind of coding is this ? You are breaking all the concepts of OOPS.
Problem with your existing code and must be resolved first -
Class UserInfo
You are creating datamembers public. It is breaking the concept of data hiding. Your datamember should be assigned values through the functions .
Instead of :
public $errors = array();
public $messages = array();
use:
private $errors = []; PHP >=5.4
private $messages = []; PHP >=5.4
Constructors should not be used for these kind of operation.
Create a wrapper class for handling user Info.
Create a new Class that will handle all the database related queries.
You can extend this class into UserInfo class. This way, your code will not look beautiful but would be more scalable.
So I remove your construct
If you are using autoincrement field,it will automatically increase the id so do not use NULL for this. Insert statement should have proper mapping.
Please use exception handling instead using condition for errors.
Class Login
Again data hiding concept missing
Don't use session start in the constructor instead of this use session start in the very first line of your entry script.
remove constructor from this class because of no use.
change your access specifier to public for login and logout
Since, you have already created a class for database query handling (please create if you still not created.)
Session destory will kill all the sessions of your website at one go. Please be specific to the session you want to destroy.
Number of table column and values in insert query are equal.
For Example
INSERT INTO table_name (column1, column2, column3)
VALUES (value1, value2, value3)
So your insert query would be
$insert = "insert into users_info (user_location) values ( '".$user_location."')";
I'm in the process of learning how to use the CI framework and am currently working on a user login form. Haven't created a user registration yet, so I'm manually adding credentials into the database. Since I'm testing everything locally, I decided to give crypt a try with no salt which is probably not the best method. I'm using form validation and a callback to check the form data against the information in the database.
here is a snippet from the users controller:
function password_check($password) {
$username = $this->input->post('username', TRUE);
$password = Modules::run('security/create_hash', $password);
$this->load->model('mdl_users');
$result = $this->mdl_users->password_check($username, $password);
if ($result == FALSE) {
//$this->form_validation->set_message('password_check', 'Please login using the correct credentials!');
//return FALSE;
echo $password;
echo '<br/><br/>';
echo $result;
echo '<br/><br/>';
}
else {
return TRUE;
}
}
I echoed the password and the result for testing and password is showing as hashed.
Here is the password_check method:
function password_check($username, $password) {
$table = $this->get_table();
$this->db->where('username', $username);
$this->db->where('password', $password);
$query=$this->db->get($table);
$num_rows = $query->num_rows();
return $num_rows;
if ($num_rows>0) {
return TRUE;
} else {
return FALSE;
}
}
I'm sure the reason this isn't working is because the password in the DB is being treated as a literal string and not as hashed, but I'm not sure as to how I can compare it as a hash.
You could do something like this: get the user from the model and then check the saved value from the password with the hashed value from the user. If you would use md5 it would look a bit like this.
controller
$this->load->model('user_model');
public function login() {
$name = $this->input->post('name');
$password = $this->input->post('password');
$user = $this->user_model->get_user_through_name($name);
if($user['password'] == md5($password)) {
//logged in
} else {
//wrong password
}
}
model
public function get_user_through_name($name) {
$query = $this->db->get_where('Users', array(
'username' => $name
));
return $query->row_array();
}
i am trying to get my head around sessions and how to use them to show user data and grant access to areas of the site etc...
i have session_start(); at the beginning of my config file, then proceed to do the connection to database etc...
i then have a User.php class which is as follows:
<?php
include('Password.php');
class User extends Password{
private $db;
function __construct($db){
parent::__construct();
$this->_db = $db;
}
public function is_logged_in(){
if(isset($_SESSION['loggedin']) && $_SESSION['loggedin'] == true){
return true;
}
}
private function get_user_hash($username){
try {
$stmt = $this->_db->prepare('SELECT password FROM members WHERE username = :username');
$stmt->execute(array('username' => $username));
$row = $stmt->fetch();
return $row['password'];
} catch(PDOException $e) {
echo '<p class="error">'.$e->getMessage().'</p>';
}
}
public function login($username,$password){
$hashed = $this->get_user_hash($username);
if($this->password_verify($password,$hashed) == 1){
$_SESSION['loggedin'] = true;
return true;
}
}
public function logout(){
session_destroy();
}
}
?>
which when i use var_dump($_SESSION); returns:
array(1) { ["loggedin"]=> bool(true) }
what i am wanting to do is have things like "Hello, '(persons username)' welcome to the '(basic,premium,admin etc...)' area of the site".
so i think i want to be doing things like $_SESSION['memberID'], $_SESSION['username'], $_SESSION['level']
so... first of all am i looking at the right area to do this kind of thing (User.php) and would modifying anything i already have allow me to do this, or do i need to create another function(s) and query the database etc... if i do things like $_SESSION['username'] = 'mee'; then it will display 'mee', but what i am wanting to do is get the username for whichever user is logged in.
*apologies in advance if this makes little sense or is confusing, iv been trying to figure this out for some time now and literally nothing im doing makes sense - brain overload :/
For security purposes it is prudent to query the database for every subsequent request to restricted areas in your application. This can be achieved by simply storing the user id in the session variable and pulling the user data with every request by the stored user id. This way if access is revoked while the user is still logged in the system will respond correctly
This functionality could be achieved like so:
// after successfully getting user by the provided username/password
$_SESSION['logged_in_user_id'] = $user['id']
Now, on every request you simply check:
if (isset($_SESSION['logged_in_user_id'])) {
$currentUser = (new User())->find($_SESSION['logged_in_user_id']);
}
In your user model you could do something like this:
public function find($id) {
try {
$stmt = $this->_db->prepare('SELECT name, address, user_level, email FROM members WHERE id = :id');
$stmt->execute(array('id' => $id));
$row = $stmt->fetchAll();
$user = new self();
$user->fill($row[0]);
return $user;
} catch(PDOException $e) {
echo '<p class="error">'.$e->getMessage().'</p>';
} catch (Exception $e) {}
return null;
}
public function fill(array $data) {
foreach ($data as $key => $value) {
$this->$key = $value;
}
return $this;
}
To log the user out:
unset($_SESSION['logged_in_user_id']);
This method has the added benefit of keeping sensitive user data out of the plain text session files on your server which could be read by anyone with access to the box.
Now, with sessions you do need to keep in mind the potential for session hijacking but that is another discussion and there are tons of PHP packages out there to help protected against this vulnerability.
If you are not concerned with security and just want to get it working with as little effort as possible (not recommended) you can simply store the user data array in the session and use whatever you need from it:
Log in code:
public function login($username,$password){
if ($user = $this->getWithCredentials($username, $password) {
$_SESSION['logged_in_user'] = json_encode($user);
return true;
}
return false;
}
public function getWithCredentials($username, $password) {
try {
$hashedPassword = $this->myPasswordHashAlgorithm($password);
$stmt = $this->_db->prepare('SELECT id, name, address, email FROM members WHERE username = :username AND password = :password');
$stmt->execute(array('username' => $username, 'password' => $hashedPassword));
$row = $stmt->fetch();
return $row;
} catch(PDOException $e) {
echo '<p class="error">'.$e->getMessage().'</p>';
} catch (Exception $e) {}
return null;
}
Then, to get user data on subsequent requests:
$user = isset($_SESSION['logged_in_user']) ? json_decode($_SESSION['logged_in_user'] : null;
If I declare a global variable such as a database connection of $mysqli how do I use that in a class. i am trying to use it in my user class. Do i store it as a public variable in the class or as a global in the function itself. I also think there is something wrong with my following code but I may be wrong.
class USER
{
function __constructor()
{
}
/*
* adds a new user
* returns FALSE on error
* returns user id on success
*/
function add_member($name, $email, $password)
{
global $mysqli;
$query = "INSERT INTO members
SET
user_name = {'$name'},
user_email = {'$email'},
password = ['$password'}";
$success = $mysqli -> query ($query);
if (!$success || $mysqli -> affected_rows == 0)
{
echo "<p> An error occurred: you just are not tough enough!!!</p>";
return FALSE;
}
$uid = $mysqli -> insert_id;
return $uid;
}
} // end class
$uc = new USER();
?>
<?php
require_once ('includes/classes/database.php');
require_once('includes/classes/user.php');
require_once('includes/header.php');
// if user submits a new registration
if (isset($_POST['name'],$_POST['email'],$_POST['pwd'],$_POST['pwd2']))
{
// validate input fields
$name = $_POST['name'];
$email = $_POST['email'];
$password = $_POST['pwd'];
$password2 = $_POST['pwd2'];
// if error fall through and redisplay page with errors
// if no errors update database and redirect to homepage
if ($uc->add_member($name, $email, $password) === FALSE)
{
echo "System Error. damn if I know what to do";
}
else
{
header("location: homepage.php");
}
}
You um... don't. Instead use a variable inside of the class:
class USER
{
private $mysql;
function __constructor($mysqli)
{
$this->mysqli = $mysqli;
}
function add_member($name, $email, $password)
{
$mysqli = $this->mysqli;
/* yada yada */
Couple of issues by the way:
// You want the ' outside of the {}
$query = "INSERT INTO members
SET
user_name = '{$name}',
user_email = '{$email}',
password = '{$password}'";// there was a [ not a {
You also want to call mysqli_real_escape_string on all of those variables. Or better yet use mysqli_bind_param and a prepared statement.