Gopesh wrote:Thanks a lot Pazuzu156 for this wonderful login script using sessions.Surely it will help all the beginners to develop a login form with enhanced security....
Login scripts always seem to cause the most questions on this board, why, because login scripts are HARD!
I don't like Pazuzu's script. While it seemingly accomplishes the task, it's not done well. It's not doing you any favors by using it, if you are concerned with security. I picked 3 points about his script to focus on:
1) Use the MySQLi extenstion.
2) NEVER EVER EVER use a hash algorithm like md5 for storing passwords, that goes for SHA1 as well. Never store a password hash that has not been salted.
3) ALWAYS validate your input.
4) Pay attention to Character Encoding
I spent some time writing this script in reply to your post. It's not the be-all end-all, in fact, the contrary. It contains only the bare minimum functionality, and it is expected that you will customize it and improve it for your needs. It consists of 3 files, 2 of which are template files. It's kind of long, but it's important to understand why certain steps are done.
I hope this helps, good luck.
index.php
Code: Select all
<?php declare(encoding = 'UTF-8');
/*
* Schema
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`user_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_username` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
`user_password` char(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`user_password_salt` char(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`user_email` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
*/
# Configuration
# Debugging - To be turned off before relase
error_reporting(E_ALL);
ini_set('display_errors', 'On');
# URI's
define('WWW_ROOT', 'www.example.org');
# Encoding
define('DEFAULT_CHARACTER_SET', 'utf8');
define('DEFAULT_COLLATION', 'utf8_unicode_ci');
# Database Credentials
define('DB_HOSTNAME', 'localhost');
define('DB_USERNAME', 'myUsername');
define('DB_PASSWORD', 'myPassword');
define('DB_DATABASE', 'myDatabase');
define('DB_PORT', '3306');
# Hash Pepper
define("HASH_PEPPER", "OTEyNjY1MDg0NzY3Y2E2M2ZmMzA0ZDE4NzQ0NGFjNjY3NDlkOGFkYThkZWE0ZDQ4YzAyNGE1ZjYzNDcxMmI4YjRjM2UxMmVjYmNjNDcxNDA1OGVlODlmZTExOGE5YmNkOGZjZGI2NWMxOTg5MjM1OGIwNDE1MzY4OTI4ZWQ4OTY=");
# Validation Constraints
define("CONSTR_EMAIL_MAX_LENGTH", 128);
define("CONSTR_EMAIL_MIN_LENGTH", 6);
define("CONSTR_PASSWORD_MIN_LENGTH", 6);
define("CONSTR_USERNAME_MAX_LENGTH", 32);
define("CONSTR_USERNAME_MIN_LENGTH", 3);
# Regular Expressions
define('REGEX_EMAIL_ADDRESS','/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/ui');
define('REGEX_USERNAME','/^[A-Z][A-Z0-9_]{' . (CONSTR_USERNAME_MIN_LENGTH - 1) . ',' . (CONSTR_USERNAME_MAX_LENGTH - 1) . '}$/ui');
# Start Session
session_start();
# Database Connection
$database = new mysqli(DB_HOSTNAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE, DB_PORT);
if($database->connect_error)
exit('Connect Error (' . $database->connect_errno . '): ' . $database->connect_error);
# Set Default Character Set and Collation
$database->set_charset(DEFAULT_CHARACTER_SET);
$database->query("SET NAMES '" . DEFAULT_CHARACTER_SET . "' COLLATE '". DEFAULT_COLLATION ."';");
# Basic Functions
function is_https() {
# A hack for an example.
return true;
if(isset($_SERVER['HTTPS']) && mb_strtolower($_SERVER['HTTPS']) == "on")
return true;
else if(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 1)
return true;
else if(isset($_SERVER['SERVER_PORT']) && $_SERVER["SERVER_PORT"] == 443)
return true;
else
return false;
}
function is_postback() {
return (isset($_SERVER['REQUEST_METHOD']) && mb_strtolower($_SERVER['REQUEST_METHOD']) == "post");
}
function redirect($location, $replace = true, $code = 302) {
header("location:" . $location, $replace, $code);
exit();
}
function generate_salt($length = 32) {
$fp = @fopen('/dev/urandom', 'rb');
if($fp) {
$random = fread($fp, $length);
fclose($fp);
return bin2hex($random);
}
return false;
}
function hash_password($salt, $pepper, $password, $algorithm = 'SHA512') {
return hash($algorithm, $salt . $pepper . $password);
}
function is_logged_in() {
if(isset($_SESSION['user_id']) && $_SESSION['user_id'] > 0)
return true;
else
return false;
}
# Process Request
$request = isset($_GET['request']) ? $_GET['request'] : '';
# Route Request - Whitelist
switch($request) {
case 'register':
# Is this a secure POST request?
if(is_https() && is_postback()) {
# Verify the POST vars exist before referencing them
$username = isset($_POST['username']) ? $_POST['username'] : '';
$password = isset($_POST['password']) ? $_POST['password'] : '';
$password2 = isset($_POST['password2']) ? $_POST['password2'] : '';
$email = isset($_POST['email']) ? $_POST['email'] : '';
$email2 = isset($_POST['email2']) ? $_POST['email2'] : '';
# Verify that $username, $password, and $email are not empty
if(!empty($username) && !empty($password) && !empty($email)) {
# Compare passwords
if($password !== $password2)
redirect(WWW_ROOT . '/index.php?register_error=' . urlencode('Passwords do not match')); // Passwords don't match - Redirect
else
unset($password2);
# Compare emails
if($email !== $email2)
redirect(WWW_ROOT . '/index.php?register_error=' . urlencode('Email Addresses do not match')); // Emails don't match - Redirect
else
unset($email2);
# Validate the username here.
# Check for min/max length requirements, invalid characters, proper format
if(!preg_match(REGEX_USERNAME, $username))
redirect(WWW_ROOT . '/index.php?register_error=' . urlencode('Invalid Username')); // Invalid Username - Redirect
# You should validate the password here.
# Check for min length requirements
if(mb_strlen($password) < CONSTR_PASSWORD_MIN_LENGTH)
redirect(WWW_ROOT . '/index.php?register_error=' . urlencode('Password not long enough')); // Password insufficient length - Redirect
# Validate the email address here.
# Check for min/max length requirements, invalid characters, proper format
if(mb_strlen($email) < CONSTR_EMAIL_MIN_LENGTH || mb_strlen($email) > CONSTR_EMAIL_MAX_LENGTH)
redirect(WWW_ROOT . '/index.php?register_error=' . urlencode('Invalid Email Address')); // Invalid Email - Redirect
if(!preg_match(REGEX_EMAIL_ADDRESS, $email))
redirect(WWW_ROOT . '/index.php?register_error=' . urlencode('Invalid Email Address')); // Invalid Email - Redirect
# Verify username does not already exist in the database
$records = $database->query(sprintf("SELECT `user_id` FROM `users` WHERE `user_username` = '%s';",
$database->real_escape_string($username)));
if($records->num_rows > 0) {
# Username Already Exists
redirect(WWW_ROOT . '/index.php?register_error=' . urlencode('Username already taken')); // Username already exists - Redirect
} else {
# Username Available
$salt = generate_salt();
$password = hash_password($salt, HASH_PEPPER, $password);
# Add New User
$database->query(sprintf("INSERT INTO `users` (`user_username`, `user_password`, `user_password_salt`, `user_email`) VALUES ('%s', '%s', '%s', '%s');",
$database->real_escape_string($username),
$database->real_escape_string($password),
$database->real_escape_string($salt),
$database->real_escape_string($email)));
# Verify Success
$user_id = $database->insert_id;
if($user_id) {
# Log User In
$_SESSION['user_id'] = $user_id;
# Take the user to their homepage
redirect(WWW_ROOT . '/index.php'); // Login Sucessful - Redirect
} else {
# Unsuccessful - possible query error
redirect(WWW_ROOT . '/index.php?register_error=' . urlencode('Unable to process your request. Try again later.')); // We have a problem with our database server, don't inform the user - Redirect
}
}
} else {
# Required Form Fields Missing
redirect(WWW_ROOT . '/index.php?register_error=' . urlencode('Please fill in all form fields')); // Redirect
}
}
# Make sure this page is not rendered
redirect(WWW_ROOT . '/index.php'); // Redirect to Homepage
break;
case 'login':
# Is this a secure POST request?
if(is_https() && is_postback()) {
# Verify the POST vars exist before referencing them
$username = isset($_POST['username']) ? $_POST['username'] : '';
$password = isset($_POST['password']) ? $_POST['password'] : '';
# Verify that $username and $password are not empty
if(!empty($username) && !empty($password)) {
# Validate the username here.
# Check for min/max length requirements, invalid characters, proper format
if(!preg_match(REGEX_USERNAME, $username))
redirect(WWW_ROOT . '/index.php?login_error=' . urlencode('Invalid Username')); // Invalid Username - Redirect
# Fetch Records From the Database that Match $username
$records = $database->query(sprintf("SELECT `user_id`, `user_password`, `user_password_salt` FROM `users` WHERE `user_username` = '%s';",
$database->real_escape_string($username)));
# Verify Number of Records
if($records->num_rows == 1) {
# Found 1 record matching username
$record = $records->fetch_assoc();
# Check that the supplied password matches whats stored in the database
if(hash_password($record['user_password_salt'], HASH_PEPPER, $password) == $record['user_password']) {
# Do not store user credentials in sessions or cookies!!!
$_SESSION['user_id'] = $record['user_id'];
# Take the user to their homepage
redirect(WWW_ROOT . '/index.php'); // Login Sucessful - Redirect
} else {
# Incorrect Password
redirect(WWW_ROOT . '/index.php?login_error=' . urlencode('Incorrect Username/Password')); // Username not found - Redirect
}
} else if($records->num_rows > 1) {
# Too many users found - Problem with database
redirect(WWW_ROOT . '/index.php?login_error=' . urlencode('Unable to process your request. Try again later.')); // We have a problem with our database server, don't inform the user - Redirect
} else {
# Username not found - try again
redirect(WWW_ROOT . '/index.php?login_error=' . urlencode('Incorrect Username/Password')); // Username not found - Redirect
}
}
}
# Make sure this page is not rendered
redirect(WWW_ROOT . '/index.php'); // Redirect to Homepage
break;
case 'logout':
# Is this a secure request and is the user logged in?
if(is_https() && is_logged_in())
unset($_SESSION['user_id']);
# Take the user to the homepage
redirect(WWW_ROOT . '/index.php'); // Take the user to the home page - Redirect
break;
default:
# Display Homepage
if(is_logged_in()) {
include_once('includes/my_account.php');
} else {
include_once('includes/homepage.php');
}
break;
}
# Close database connection
$database->close();
?>
includes/homepage.php
Code: Select all
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Better Registration/Login Example</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="Author" content="Devnetwork: Flying_Circus" />
</head>
<body>
<h1>Login</h1>
<form action="index.php?request=login" method="post" accept-charset="UTF-8">
<div>
<?php
$login_error = isset($_GET['login_error']) ? $_GET['login_error'] : '';
if(!empty($login_error))
print "<p>Error: {$login_error}</p>";
?>
Username: <input type="text" name="username" value="" /><br />
Password: <input type="password" name="password" value="" /><br />
<input type="submit" value="Login" />
</div>
</form>
<br />
<br />
<hr />
<br />
<h1>Register</h1>
<form action="index.php?request=register" method="post" accept-charset="UTF-8">
<div>
<?php
$register_error = isset($_GET['register_error']) ? $_GET['register_error'] : '';
if(!empty($register_error))
print "<p>Error: {$register_error}</p>";
?>
Username: <input type="text" name="username" value="" /><br />
Password: <input type="password" name="password" value="" /><br />
Confirm Password: <input type="password" name="password2" value="" /><br />
Email: <input type="text" name="email" value="" /><br />
Confirm Email: <input type="text" name="email2" value="" /><br />
<input type="submit" value="Register" />
</div>
</form>
</body>
</html>
includes/my_account.php
Code: Select all
<?php
# Fetch User Info
$records = $database->query(sprintf("SELECT `user_username` FROM `users` WHERE `user_id`='%s';",
$database->real_escape_string($_SESSION['user_id'])));
if($records->num_rows == 1) {
$user = $records->fetch_assoc();
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Better Registration/Login Example</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="Author" content="Devnetwork: Flying_Circus" />
</head>
<body>
<h1>My Account</h1>
<br />
<p>Hi, <?php print htmlentities($user['user_username']); ?>!</p>
<br />
<a href="index.php?request=logout">Logout</a>
</body>
</html>