i bet there are scripts out there already about this, but I'm creating this project just for fun and to test my knowledge, now i just want the public's opinions, and if you guys find a way I could improve feel free to share as well to comment against it.
My question is simply how to create a good salt. after reading the manual, and a few book chapters this is what i came up with. Although i feel like my salt should be longer for security. what should I change?
Here is my user class. please check genSalt() function and guide me to figure out how to improve my results.
<?php
if(!defined('ACCESS_CORE')){
echo 'Permission Not Granted';
exit;
}
class user{
private $_email;
private $_pass;
private $_db;
private $_url;
function __construct(){
$this->_db = $this->db();
$this->_url = 'localhost'; //change this to ur url
if(isset($_POST['user_login'])){
$this->_email = $this->clean($_POST['user_email']); //sanitize later
$this->_pass = $this->clean($_POST['user_password']);
}
}
protected function db(){
$db = parse_ini_file('../contra.conf');
$this->_db = new mysqli($db['host'], $db['user'], $db['pass'], $db['name']);
if ($this->_db->connect_errno) {
trigger_error("Failed to connect to MySQL".$mysqli->connect_errno). $mysqli->connect_error;
}
}
protected function clean($string){
return mysql_real_escape_string($string); #TODO: add more options html etc
}
public function safeReferer(){
$ref = (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''); //if there is a ref..
if(empty($ref) || strpos($ref, $this->_url)){
return true;
} else {
return false;
}
}
public function includeForm($message = ""){ #TODO: finish form view page
?>
<div id="logForm">
<h3>User Authentication Form</h3>
<?php echo ($message === "") ? '' : $message; ?>
<form id="loginForm" method="post" action="login.php">
<input type="text" name="user_email" />
<input type="password" name="user_password" />
<input type="submit" value="Login" name="user_login" />
<a href="/" >Forgot password?</a>
</form>
</div>
<?php ;
}
protected function genSalt($length) { #TODO: improve something is fishy
$prefix = '$2a$'.$length.'$'; //blowfish prefix
//base64 unique random alphanumeric
$uniqRand = base64_encode(mcrypt_create_iv($length, MCRYPT_DEV_URANDOM));
$modified_string = str_replace('+', '.', $uniqRand);
$salt = substr($modified_string, 0, $length);
return $prefix.$salt.'$';
}
protected function correctPass($password, $salt){ #TODO: change to prepared statement. best method?
$sql = "SELECT pass, s FROM users WHERE email = '$this->_email'";
if($result = $this->_db->query($sql)){
while ($row = $result->fetch_object()) {
if(cript($row['pass'], $row['s']) === $row['s']){
return true;
} else {
return false;
}
}
}
}
public function login(){
if($this->correctPass($this->_email, $this->_pass)){
echo 'create session, session cookie, start timeout, and redirect'; #TODO: copy login, finish page on form view
} else {
$message = '<h5>Please try again</h5>';
$message .= '<p>It looks like you have either entered a wrong user name or password.';
$this->includeForm($message);
}
}
// test function, similar function in register class
public function createPass($pass){
$salt = $this->genSalt(10);
$hash = crypt($pass, $salt);
echo $salt. '--';
echo 'hashed pass : '. $hash;
echo '<br> entered pass : '.$pass.'<br>';
if(crypt($pass, $hash) == $hash ){
echo 'true';
} else {
echo 'false';
}
}
}
?>
test function results...
$2a$10$WlUvRqsgZl$--
hashed pass : $2a$10$WlUvRqsgZl$$$$$$$$$$$. tRNdwECDQXhN07g4mIp82xxFCTUev3m
entered pass : mypassword
true
Why not consider the password_hash function? It also hashes but generates a random salt every time and uses blowfish by default. It requires PHP 5.5 or later, however.
Related
I have a problem with my login the login works it checks the html forms and it the info is correct it will login me to the logged in page.I set the username and user id on the login page after the info is checked but the server seems like it wont save the info. Strange thing is if i logout then use the login the site works registers the username etc on the login i check the info then set the username into a variable then do
if(empty($_SESSION['username']))
{
echo "Somthing went wrong";
echo '<META HTTP-EQUIV="Refresh" Content="0; URL=logout.php">';
}
But this does not trigger again once i go to the site for the first time in a while i need to view the logout page before i can logout even tho im not logged in of course the logout only destroys the session.
if (isset($_POST["Username"]) && !empty($_POST["Username"])) {
$salt= "";
$username23 = mysql_real_escape_string($_POST['Username']);
$thereusername = strip_tags($username23);
$password2= sha1 ($salt.$_POST["password"]);
$statement = $db->prepare("SELECT * FROM users WHERE username = ? AND password = ? ");
$statement->execute(array($thereusername,$password2));
$count = $statement->rowCount();
/// If usernam and password match we carry on
if ($count == "1"){
$username23 = mysql_real_escape_string($_POST['Username']);
$thereusername = strip_tags($username23);
$statement8 = $db->prepare("SELECT * FROM users WHERE username = ? ");
$statement8->execute(array($thereusername));
$count8 = $statement8->fetch();
$username233 = mysql_real_escape_string($count8['id']);
$_SESSION['userid'] = strip_tags($username233);
$_SESSION['username'] = $thereusername ;
if(empty($_SESSION['username'])){
echo "Somthing went wrong";
echo '<META HTTP-EQUIV="Refresh" Content="0; URL=logout.php">';
}
if(empty($_SESSION['userid']))
{
echo "Somthing went wrong";
echo '<META HTTP-EQUIV="Refresh" Content="0; URL=logout.php">';
}
echo "You are now being logged in";
echo '<META HTTP-EQUIV="Refresh" Content="0; URL=dashboard.php">';
exit;
echo "works";
}
};
?>
.
<form action="login.php" method="post">
<div class="row">
<div class="form-group col-sm-6">
<label for="exampleInputEmail1">Username</label>
<input class="form-control" type="text" value="Artisanal kale" name= "Username" id="Username">
</div>
<div class="form-group col-sm-6">
<label for="exampleInputPassword1">Password</label>
<input type="password" class="form-control" name= "password" id="password"placeholder="Password">
</div>
</div>
<div class="row">
<div class="col-sm-4">
<p> <input type="submit" value="Submit" class="btn theme-btn"> </p>
</div>
</div>
</form>
I of course use the session start at the top of the page after i login it takes me to the logged in page but will not display the username or any info ive done print_r session and get Array ( ) i then go to logout.php and login and all works perfect server error or php error ?
First off, don't do your own password salt/encrypt, you need to use password_hash() to save password hashes and password_verify() (or bcrypt equivalent) to check the hashed password vs submitted password. Second, as noted, you need to use PDO exclusively. Lastly, you need to make some classes to make your script more manageable and easier to troubleshoot. This is more complex because there are a lot of parts that need to be implemented but this is an basic example of where you should be going with your login and such. I would advise, if you don't understand the majority of this, you probably should download a framework because this type of thing is complex to do it right. Frameworks have this all built in, you just have to program the very top level of the script, for the most part...
/core/classes/App.php
class App
{
# Storage of global arrays
protected static $GlobalArray = array();
# Returns post trimmed POST array
public function getPost($key=false)
{
if(!isset(self::$GlobalArray['_POST']))
self::$GlobalArray['_POST'] = $this->sanitizeArray($_POST);
if(!empty($key))
return (isset(self::$GlobalArray['_POST'][$key]))? self::$GlobalArray['_POST'][$key] : false;
return self::$GlobalArray['_POST'];
}
# Trims the values
public function sanitizeArray($array)
{
if(!is_array($array))
return trim($array);
foreach($array as $key => $value) {
$array[$key] = $this->sanitizeArray($value);
}
return $array;
}
}
/core/classes/User.php
class User extends App
{
private $con;
public function __construct(\PDO $con)
{
$this->con = $con;
}
public function savePassword($username,$password)
{
# Create the password hash
$hash = password_hash($password);
# Prepare the query and store password hash
$query = $this->con->prepare("UPDATE users SET `password` = ? WHERE `username` = ?");
$query->execute(array($username,$password));
return $this;
}
public function validateUser($username,$password)
{
# Prepare the query to get the user
$query = $this->con->prepare("SELECT * FROM users WHERE `username` = ? LIMIT 1");
$query->execute(array($username));
# Assign password
$user = $query->fetch(\PDO::FETCH_ASSOC);
if(empty($user['password']))
return false;
# Match hash to password
if(!password_verify($password,$user['password']))
return false;
# Return the user data
return $user;
}
}
/core/classes/Session.php
class Session extends App
{
public function toSession($array)
{
foreach($array as $key => $value) {
$_SESSION[$key] = $value;
}
}
# Save to errors array
public function toError($array)
{
foreach($array as $key => $value) {
$_SESSION['errors'][$key] = $value;
}
}
# Get error
public function getError($key=false)
{
if(!empty($key))
return (isset($_SESSION['errors'][$key]))? $_SESSION['errors'][$key] : false;
return (isset($_SESSION['error']))? $_SESSION['error'] : false;
}
# Get value
public function get($key=false)
{
if(!empty($key))
return (isset($_SESSION[$key]))? $_SESSION[$key] : false;
return (isset($_SESSION))? $_SESSION : false;
}
public function start()
{
session_start();
}
public function destroy($key=false)
{
if(!empty($key)) {
if(isset($_SESSION[$key])) {
$_SESSION[$key] = null;
unset($_SESSION[$key]);
}
}
else {
session_destroy();
}
}
}
/config.php
# Create important defines
define('DS',DIRECTORY_SEPARATOR);
define('ROOT_DIR',__DIR__);
define('CORE',ROOT_DIR.DS.'core');
define('CLASSES',CORE.DS.'classes');
define('FUNCTIONS',ROOT_DIR.DS.'functions');
# A class autoloader is a must...
spl_autoload_register(function($class){
$path = str_replace(DS.DS,DS,CLASSES.DS.str_replace('\\',DS,$class).'.php');
if(is_file($path))
include_once($path);
});
# Include connection
include(FUNCTIONS.DS.'functions.php');
# Create connection
$db = mysqlconnect();
# Start the session
$Session = new Session();
$Session->start();
/login.php
# Add our config file
require_once(__DIR__.DIRECTORY_SEPARATOR.'config.php');
# Create application
$App = new User($db);
# Check if submission login
if(!empty($App->getPost("Username"))) {
# Get the user array (returns on validated)
$User = $App->validateUser($App->getPost("Username"),$App->getPost("password"));
# If user is valid
if($User){
$Session->toSession(array(
'userid'=>$User['id'],
'username'=>$User['username']
));
# Redirect & stop
header('Location: dashboard.php');
exit;
}
else {
# Store the error
$Session->toError(array("invalid_login"=>"Invalid username or password"));
# Redirect to error or whatever...
}
}
I haven't really checked this, but I have noted so you know what does what (or is supposed to do). You want to always include the config.php at the top of every top level page for consistency.
Use print_r($Session->get()); to see the session array. Also note the password check won't work if you haven't saved the password hash properly.
Im trying to use ajax to submit a form and return type either Business or Admin but I'm getting:
JSON.parse: unexpected end of data
result= JSON.parse(r);
<input type="text" id="signinemail" placeholder="Email" name="signinemail">
<input type="password" id="signinpassword" placeholder="Password"
name="signinpassword">
<script>
$(function() {
$("#signinsubmit").click(function() {
var username = $("#signinemail").val();
$.post("signin.php",
{
signinusername: username, signinpassword: $("#signinpassword").val()
} )
.done( function(r)
{
result= JSON.parse(r);
if(result["user_type"]=="Business")
{
window.location="profile.php";
}
else if(result["user_type"]=="Admin")
{
window.location="requestpage.php";
}
});
});
});
</script>
This is the class that trying to login in with. It firsts takes the post gives it to the authenticate function then returns the result of the connection to the log in function that encodes it
<?php
/**
* Logs the User into Website
*/
class Login
{
private $connection;
private $result_array = array();
private $user_type;
private $id;
public $username;
private $password;
public $loggedIn;
function __construct()
{
$this->username = $_POST['signinemail'];
$this->password = $_POST['signinpassword'];
$this->connection = new mysqli('WolfeboroC.db.10688096.hostedresource.com', 'WolfeboroC', 'Brewster#1', 'WolfeboroC');
$this->authenticate();
$this->logIn($this->authenticate);
}
private function authenticate()
{
$query = "SELECT recid, Admin FROM users
WHERE User = '".$this->$username."'
AND password='".$this->$password."'
AND (verified='y' OR admin = 'y')
LIMIT 1";
$stmt = mysqli_master_query($this->connection, $query);
$this->result_array = mysqli_fetch_array($stmt);
return !empty($this->result_array);
}
private function logIn()
{
if($result_array->num_rows > 0)
{
if($result_array['Admin']=='y')
{
$this->user_type = "Admin";
$this->admin($this->result_array);
$this->loggedIn = true;
}
else
{
$this->user_type = "Business";
$this->business($this->result_array);
$this->loggedIn = true;
}
echo json_encode($this->user_type['user_type']);
}
}
}
?>
echo json_encode($this->user_type['user_type']); is not correct. Your user_type is not an array so don't try to access it like this. You either do a echo $this->user_type and use the result as a string in javascript OR put the value in an array and then json_encode it like this:
echo json_encode(array('user_type' => $this->user_type));
Try using to get json response as,
result.user_type
instead of
result["user_type"]
In login() function: json synatx should be
echo json_encode(array('user_type' => $this->user_type));
instead of,
echo json_encode($this->user_type['user_type']);
After submitting my form below via ajax the message always comes back as failed, even with the correct login information! I've coded this in a non oop style too and it works perfectly, but when i use this style of code it hangs up. The live site is http://andyholmes.me/demo/summersproperty/OOP/login.php and the username is admin#summersproperty.com and password is admin
login.php -
<?PHP
session_start();
include('includes/class.login.php');
$login = new Login();
$token = $_SESSION['token'] = md5(uniqid(mt_rand(), true));
if ($_POST['ajax']) {
exit($login->getStatus());
}
?>
<style>
#message { display: none; cursor: pointer; }
.loader { display: none; }
</style>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script>
$(document).ready(function(){
$("#loginForm").submit(function(e) {
$(this).fadeOut(300);
$('.loader').delay(300).fadeIn(100);
$.post("<?=$_SERVER['PHP_SELF'];?>", { username: $('#username').val(), password: $('#password').val(), ajax: true }).done(function(data) {
if (data.logged_in == true) {
// Redirect with javascript
$('.loader').delay(2000).fadeOut(100);
$('#message').html('<p>Success! We\'ll redirect you in a minute...</p>').delay(2200).fadeIn(200);
} else {
$('.loader').delay(2000).fadeOut(100);
$('#message').html('<p>Failed... Click to try again!').delay(2200).fadeIn(200);
$('#message').on('click', function(){
$(this).fadeOut(200);
$('#loginForm').delay(350).fadeIn(200);
});
}
});
return false;
});
});
</script>
<form id="loginForm" method="POST" action="">
<table>
<tr><td>Username:</td><td><input type="text" name="username" id="username"/></td></tr>
<tr><td>Password:</td><td><input type="password" name="password" id="password"/></td></tr>
</table>
<input type="hidden" name="token" value="<?=$token;?>"/>
<input type="submit" name="login" value="Log In"/>
</form>
<div class="loader">
<img src="loader.gif"/>
</div>
<div id="message"></div>
and the login class -
<?PHP
class Login
{
private $_id;
private $_username;
private $_password;
private $_passmd5;
private $_errors;
private $_access;
private $_login;
private $_token;
public function __construct()
{
$this->_errors = array();
$this->_login = isset($_POST['login']) ? 1 : 0;
$this->_access = 0;
$this->_token = $_POST['token'];
$this->_id = 0;
$this->_username = ($this->_login) ? $this->filter($_POST['username']) : $_SESSION['username'];
$this->_password = ($this->_login) ? $this->filter($_POST['password']) : '';
$this->_passmd5 = ($this->_login) ? md5($this->_password) : $_SESSION['password'];
}
public function isLoggedIn()
{
($this->_login) ? $this->verifyPost() : $this->verifySession();
return $this->_access;
}
public function filter($var)
{
return preg_replace('/[^a-zA-Z0-9]/','',$var);
}
public function verifyPost()
{
try
{
if(!$this->isTokenValid())
throw new Exception('Invalid form submission');
if(!$this->isDataValid())
throw new Exception('Invalid form data entered');
if(!$this->verifyDatabase())
throw new Exception('Invalid username/password combination');
$this->_access = 1;
$this->registerSession();
}
catch(Exception $e)
{
$this->_errors[] = $e->getMessage();
}
}
public function verifySession()
{
if($this->sessionExist() && $this->verifyDatabase())
$this->_access = 1;
}
public function verifyDatabase()
{
include('dbConfig.php');
$data = mysql_query("SELECT user_id FROM users WHERE user_username = '{$this->_username}' AND user_password = '{$this->_passmd5}'");
if(mysql_num_rows($data))
{
list($this->_id) = #array_values(mysql_fetch_assoc($data));
return true;
}
else
{
return false;
}
}
public function isDataValid()
{
return (preg_match('/^[a-zA-Z0-9]/', $this->_username) && preg_match('/^[a-zA-Z0-9]/', $this->_password)) ? 1 : 0;
}
public function isTokenValid()
{
return (!isset($_SESSION['token']) || $this->_token != $_SESSION['token']) ? 0 : 1;
}
public function registerSession()
{
$_SESSION['id'] = $this->_id;
$_SESSION['username'] = $this->_username;
$_SESSION['password'] = $this->_passmd5;
}
public function sessionExist()
{
return (isset($_SESSION['username']) && isset($_SESSION['password'])) ? 1 : 0;
}
public function showErrors()
{
echo "<h3>Errors</h3>";
foreach($this->_errors as $key=>$value)
echo $value."<br>";
}
public function getStatus()
{
return json_encode(array('logged_in' => $this->isLoggedIn(), 'errors' => $this->showErrors()));
}
}
?>
By the way, i know i need to use PDOs etc, but i just want to get the script to a point where it works nicely before i change the database connection data. I know im close, but its really frustrating!
If you can help me, i will be most grateful!
EDIT NOTES: This code has been updated for an issue that has come up after using the suggestion from user1781670
<?PHP
session_start();
include('includes/class.login.php');
$login = new Login();
$token = $_SESSION['token'] = md5(uniqid(mt_rand(), true));
if ($_POST['ajax']) {
exit($login->getStatus());
}
?>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script>
$(document).ready(function(){
$("#loginForm").submit(function(e) {
$.post("<?=$_SERVER['PHP_SELF'];?>", { username: $('#username').val(), password: $('#password').val(), ajax: true }).done(function(data) {
if (data.logged_in == true) {
// Redirect with javascript
} else {
// Inject errors to html
// data.errors
}
});
return false;
});
});
</script>
<form id="loginForm" method="POST" action="">
<table>
<tr><td>Username:</td><td><input type="text" name="username" id="username"/></td></tr>
<tr><td>Password:</td><td><input type="password" name="password" id="password"/></td></tr>
</table>
<input type="hidden" name="token" value="<?=$token;?>"/>
<input type="submit" name="login" value="Log In"/>
</form>
As you can see I modified your jquery removing you PHP code inside because that's not the place where it goes, also I changed the syntax a little to one more clear at least for me. Also note that "data" is a json returned by your PHP function getStatus who returns the login status as json.
Now you just need to create the PHP function that return the json. Maybe can help you to checkout json_encode. If you get stuck please tell us.
Example of getStatus function:
JavaScript objects are like associate arrays in PHP except JavaScript objects can have functions. So, is not surprise you need to pass an associative array to json_encode.
public function getStatus()
{
return json_encode(array('logged_in' => $this->isLoggedIn(), 'errors' => $this->showErrors()));
}
$.post automatically knows it received a JSON (it's the default option), so you can access it's properties with data.logged_in and data.errors.
This is the problem: you show your login form and when the user submit the form, through ajax you open a connection and send the data entered by the user and you expect the server to return information. But how is that data gonna be returned? how are you gonna handle it? well, that is JSON for. It's a syntax to write JavaScript objects, so with json_encode you return a JSON and when your JavaScript receives that JSON you can access it's data and check if it was a successful login.
So I have a problem with updating a mysql table via mysqli in php.
The database connection and class:
<?php
class testDB extends mysqli {
private static $instance = null;
private $user = "tester";
private $pass = "tester01";
private $dbName = "testdb";
private $dbHost = "localhost";
public static function getInstance() {
if (!self::$instance instanceof self) {
self::$instance = new self;
}
return self::$instance;
}
public function __clone() {
trigger_error('Clone is not allowed.', E_USER_ERROR);
}
public function __wakeup() {
trigger_error('Deserializing is not allowed.', E_USER_ERROR);
}
private function __construct() {
parent::__construct($this->dbHost, $this->user, $this->pass, $this->dbName);
if (mysqli_connect_error()) {
exit('Connect Error (' . mysqli_connect_errno() . ') '
. mysqli_connect_error());
}
parent::set_charset('utf-8');
}
public function verify_credentials ($username, $password){
$username = $this->real_escape_string($username);
$password = $this->real_escape_string($password);
$result = $this->query("SELECT 1 FROM users WHERE user_username = '" . $username . "' AND user_password = '" . $password . "'");
return $result->data_seek(0);
}
public function get_vitae() {
return $this->query("SELECT * FROM vitae");
public function update_vitae($text, $id) {
$text = $this->real_escape_string($text);
$this->query("UPDATE vitae SET vitae_text=".$text." WHERE vitae_id = ".$id);
}
}
?>
Here's the page code:
Above the header we check the login by making sure there is a session started; then import the database class and the rest is called upon resubmitting the form to this same page:
<?php
session_start();
if (!array_key_exists("username", $_SESSION)) {
header('Location: index.php');
exit;
}
require_once("includes/db.php");
$vitae_empty = false;
if ($_SERVER['REQUEST_METHOD'] == "POST") {
if ($_POST['text'] == "") {
$vitae_empty = true;
} else if ($_POST["text"]!="") {
testDB::getInstance()->update_vitae($_POST["text"], $_POST["id"]);
header('Location: manage.php' );
exit;
}
}
?>
In the body (the header and the rest of the html is imported via a 'require_once'):
<section>
<div class="grid_3 header_line"><h2>Update for CV</h2></div>
<div class="grid_3">
<?php
$result = testDB::getInstance()->get_vitae();
$vitae = mysqli_fetch_array($result);
?>
<form name="editvitae" action="editvitae.php" method="POST">
<textarea name="text" rows="50" cols="100"><?php echo $vitae['vitae_text'];?></textarea><br/>
<?php if ($vitae_empty) echo "Please enter some text.<br/>";?>
<input type="hidden" name="id" value="<?php echo $vitae["vitae_id"];?>" /> <br/>
<input type="submit" name="savevitae" value="Save Changes"/>
</form>
</div>
<div class="grid_3">
<p>‹ back to management consol</p>
</div>
</section>
After the 'body' tag:
<?php mysql_free_result($result);?>
As you can see this pulls the 'vitae' text from the database then loops it to the same page with changes to update the table. It also check's to see that the 'text' box is not empty.
This code works in another application; I'm not understanding why it won't work here. AND before you start warning me about injection and security I have stripped most of it out trying to find the problem with the update. It WILL go back in once I can figure that out.
I have tried stripping the text check; different variable names; dumping the post values into an array before updating the database; putting the post values into static variables; checking all my spellings etc...
I'm missing something and I feel like it's going to be simple.
Anytime an UPDATE is run through mysqli you need to run the $mysqli->commit(); method.
Your new update_vitae would be:
public function update_vitae($text, $id) {
$text = $this->real_escape_string($text);
$this->query("UPDATE vitae SET vitae_text=".$text." WHERE vitae_id = ".$id);
$this->commit;
}
mysqli also has an autocommit feature that can be toggled on or off:
$this->autocommit(true); //on
$this->autocommit(false); //off
So the answer was indeed simple. It was my escaping on the update string as Andrewsi suggested. Here's the update that fixed it:
public function update_vitae($text, $id) {
$text = $this->real_escape_string($text);
$this->query("UPDATE vitae SET vitae_text = '$text' WHERE vitae_id = ".$id);
}
Thanks for the help!
I've been designing websites for almost 10 years, but I'm just now getting into 'real' php coding instead of using prepared classes and dreamweaver's built in functions. Far to much to learn but it's fun in my limited spare time.
$result = $this->query("SELECT 1 FROM users WHERE user_username = '" . $username . "' AND user_password = '" . $password . "'");
Be careful with using logical ANDs for user authentication. It may be more wise to completely validate the username first before going after any kind of password. I say this in regard to the many examples of people inserting --; WHERE 1=1 -- and stuff like that (not specifically this statement, however). Sure, this may require two queries, but at least you only have to process one piece of information to determine if a visitor is valid. Another advantage might be saving processing because you won't have to deal with hashing/encrypting the user's password in your app or at the database (until the username has been verified).
In my form I have a hidden field:
<input type="hidden" name="auth_token" value="<?php echo $auth_token; ?>">
This value is also stored in a session and a variable:
$_SESSION['auth_token'] = hash('sha256', rand() . time() . $_SERVER['HTTP_USER_AGENT']); # TODO: put this in a function
$auth_token = $_SESSION['auth_token'];
When the form is submitted the two values are compared. It's a basic form token.
Should this be made into two functions or just one when refactored? set_form_token() and get_form_token(), get_form_token() returning the session value, then I can compare it in my main code. What is the proper way of doing this?
EDIT:
Considering both Joel L and RobertPitt's answers I have made these:
function set_auth_token()
{
if (!isset($_SESSION['auth_token']))
{
$_SESSION['auth_token'] = hash('sha256', rand() . time() . $_SERVER['HTTP_USER_AGENT']);
}
}
function get_auth_token()
{
if (isset($_SESSION['auth_token']))
{
return $_SESSION['auth_token'];
}
else
{
die('No auth token.');
}
}
function check_auth_token()
{
if (array_key_exists('auth_token', $_SESSION) && array_key_exists('auth_token', $_POST))
{
if ($_SESSION['auth_token'] === $_POST['auth_token'])
{
# what happens if user fills the form in wrong first time(?)
$_SESSION['auth_token'] = hash('sha256', rand() . time() . $_SERVER['HTTP_USER_AGENT']);
}
else
{
return false;
}
}
else
{
return false;
}
}
I can then check if check_auth_token returns false or not and then record it after the form has been submitted. Would this be acceptable?
In my app, I actually have the following helper functions for using tokens:
generateToken() // generate and return hash, used in login process.
// hash then saved to session
getToken() // returns user's token from session
tokenField() // shortcut for echo '<input type="hidden" ... value="getToken()" />';
// used in page templates
checkToken() // get token from either 1) $_POST 2) request header or 3) $_GET
// and compare with getToken(). generate error if invalid.
The checkToken() function checks 3 locations because the request can be GET or POST, and either of those could be via AJAX. And I have my AJAX helper automatically insert the token in the header for each request).
This way, I only need to call checkToken() everywhere the check is needed, and can therefore change the impelmentation details quite easily.
For instance, I can start using one-time tokens by changing only getToken() and checkToken().
If you manually compare if (get_form_token() == $token) everywhere in your code, you have no such flexibility.
firstly you should understand exactly what the workflow is, and Joel L explains that very simply.
You should encapsulate the methods in a class to keep everything together, some thing like sp:
class FormTokenizer
{
private $context = "";
public function __construct($auth_token = "auth_token")
{
$this->context = $context;
}
public function generateToken()
{
$_SESSION[form_tokens][$this->context] = hash('sha256', rand() . time() . $_SERVER['HTTP_USER_AGENT']);
return $this;
}
public function getToken()
{
return isset($_SESSION[form_tokens][$this->context]) ? $_SESSION[form_tokens][$this->context] : false;
}
function generateField()
{
return sprintf('<input type="hidden" name="a_%s" value="%s">',$this->context,$this->getToken());
}
public function validateToken()
{
if(isset($_POST["a_" . $this->context]))
{
return $this->getToken() == $_POST["a_" . $this->context];
}
return false;
}
}
and a simple usage would be:
$Token = new FormTokenizer("registration");
if(isset($_POST))
{
if($Token->validateToken() === false)
{
//Token Onvalid
}
}
//Generate a fresh token.
$hidden_input = $Token->generateToken()->generateField();