I'm trying to learn PHP/MySQL and the likes, so I've been reading tutorials for PHP login systems. My current iteration is based heavily on one from this website and contains the accepted answer for random salts here. This is my first thing I've done in MySQL, and my first attempt at PHP besides a tiny Tic-Tac-Toe game.
config.php:
<?php
//set off all error for security purposes
error_reporting(E_ALL);
//define some contstant
define( "DB_DSN", "mysql:host=localhost;dbname=gryp" );
define( "DB_USERNAME", "root" );
define( "DB_PASSWORD", "" );
define( "CLS_PATH", "class" );
//include the classes
include_once( CLS_PATH . "/user.php" );
?>
user.php:
<?php
class Users {
public $username = null;
public $password = null;
public $salt = null;
public function __construct( $data = array() ) {
if( isset( $data['username'] ) ) $this->username = mysql_real_escape_string( htmlspecialchars( strip_tags( $data['username'] ) ) );
if( isset( $data['password'] ) ) $this->password = mysql_real_escape_string( htmlspecialchars( strip_tags( $data['password'] ) ) );
}
public function storeFormValues( $params ) {
//store the parameters
$this->__construct( $params );
}
public function userLogin() {
$success = false;
try{
$con = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
$con->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$sql = "SELECT * FROM users WHERE username = :username LIMIT 1";
$fetch = $con->prepare( $sql );
$fetch->bindValue( "username", $this->username, PDO::PARAM_STR );
$fetch->execute();
$row = $fetch->fetch(PDO::FETCH_ASSOC);
if($row){
$this->salt=$row['salt'];
if ( hash("sha256", $this->password . $this->salt) == $row['password'])
{
$success = true;
$sql = "UPDATE users SET lastlogin=NOW() WHERE username=:username";
$fetch = $con->prepare($sql);
$fetch->bindValue( "username", $this->username, PDO::PARAM_STR );
$fetch->execute();
}
}
$con = null;
return $success;
}catch (PDOException $e) {
echo $e->getMessage();
return $success;
}
}
public function register() {
$correct = false;
try {
$con = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
$con->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$this->salt = $this->unique_md5();
$sql = "INSERT INTO users(username, password,salt,registerdate) VALUES(:username, :password,:salt,NOW())";
$fetch = $con->prepare( $sql );
$fetch->bindValue( "username", $this->username, PDO::PARAM_STR );
$fetch->bindValue( "password", hash("sha256", $this->password . $this->salt), PDO::PARAM_STR );
$fetch->bindValue( "salt", $this->salt, PDO::PARAM_STR );
$fetch->execute();
return "Registration Successful <br/> <a href='index.php'>Login Now</a>";
}catch( PDOException $e ) {
return $e->getMessage();
}
}
public function unique_md5() {
mt_srand(microtime(true)*100000 + memory_get_usage(true));
return md5(uniqid(mt_rand(), true));
}
}
?>
Table structure:
# name type collation null default extra 1 userID int(11) No None AUTO_INCREMENT 2 username varchar(50) latin1_swedish_ci No None 3 password varbinary(250) No None 4 salt varbinary(32) No None 5 registerdate datetime No None 6 lastlogin datetime No None
I think that my input is sanitized, that I'm safe from SQL injections, and that I'm safe from XSS attacks. But before I move on with what I'm doing and learn more, I figure that it's better to assume my code is insecure and ask for help, than to assume it is secure and find out it isn't.
I feel that
$this->salt=$stmt->fetchColumn(3);
shouldn't be what I'm doing. Also that I have 3 queries for login, which seems wasteful. But that was the least I could do it in.
Is my code going in the right direction? What can I do better?