Registration/Login : A Simple Tutorial


#1

After visiting this forum (and others) I have been seeing people having problems writing a login/registration system in PHP. So I decided to write a very simple one, it only has four attributes/properties: id, username, password, date_added. I just want to clarify one thing, when I said PDO was written in OOP Style I meant the code that interacts between PHP and MySQL.

I know it works for I have tested out. It is heavily commented, so this will be it with my jabbering about it. ;D
filename: common.inc.php in a folder called includes:
[php]<?php
//Start session
session_start();
// create an user $_SESSION array:
$_SESSION[‘user’] = NULL;
// Set error message to Null
$errMsg = NULL;
// Create the database connection as a PDO object:
try {

$db_options = array(
	   PDO::ATTR_EMULATE_PREPARES => false                     // important! use actual prepared statements (default: emulate prepared statements)
	   , PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION           // throw exceptions on errors (default: stay silent)
	   , PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC      // fetch associative arrays (default: mixed arrays)
   ); 		 

$pdo = new PDO('mysql:host=localhost;dbname=demo_login_system;charset=utf8', 'root', 'your_password', $db_options);	

} catch (PDOException $e) { // Report the Error!

$errMsg = "<p>Something is not right, check your php.ini settings or code</p>";

}

// A nice little function that sanitizes the data output:
function html_escape($raw_input) {
return htmlspecialchars($raw_input, ENT_QUOTES | ENT_HTML401, ‘UTF-8’); // important! don’t forget to specify ENT_QUOTES and the correct encoding
} [/php]
filename: register.php located in the root directory.
[php]<?php
/*
********* TABLE Structure *********
CREATE TABLE IF NOT EXISTS users (
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(30) NOT NULL,
password char(60) NOT NULL,
date_added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
*/

// common.inc.php file contains required
// database connection & initialization info:
require ‘includes/common.inc.php’;

// A nice password hashing library for PHP 5
// Find it here: https://github.com/ircmaxell/password_compat/blob/master/lib/password.php
// Read the Documentation for further help:
// NOTE: if you’re not using PHP 5, there are plenty of
// other good password hashing libraries out there —> JUST GOOGLE IT!
require ‘includes/password.inc.php’;

// Check to see if user has submitted form:
if (isset($_POST[‘action’]) && $_POST[‘action’] == ‘register’) {

// Grab the user's input from form:   
$username = $_POST['username'];
$password = $_POST['password'];

// Using Regex to check username:
if (preg_match("/^[0-9a-zA-Z_]{5,}$/", $username) === 0) {
    $errMsg = '<p>Username must be bigger that 5 chars and contain only digits, letters and underscore<p>';
}

// Using Regex to check password: 
if (preg_match("/^.*(?=.{8,})(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).*$/", $password) === 0) {
    $errMsg .= '<p>Password must be at least 8 characters, and must contain at least one lower case letter, one upper case letter and one digit.</p>';	    
}

// Function to check if username is available:
function isUsernameAvailable($username, $pdo) {	
	
	// The PDO Query:   
    $query = "
        SELECT
            1
        FROM users
        WHERE
            username = :username1
    ";
   
    // The prepared property/attribute:
    $query_params = array(
        ':username1' => $username
    );	

    // These two statements run the query against your database table.
    $stmt = $pdo->prepare($query);
    $result = $stmt->execute($query_params);

    // The fetch() method returns an array representing the "next" row from
    // the selected results, or false if there are no more rows to fetch.	   	   
    return $row = $stmt->fetch();	   
    // If a row was returned, then we know a matching username was found in
    // the database already and we should return a boolean value back.	   
   	      	   
}

// Check to see if username is available:
$result = isUsernameAvailable($username, $pdo);

// If username is taken then assign to $errMsg:
if ($result) {
    $errMsg .= '<p>Username: ' . $username . ' is already taken.</p>';		    
}
   		
// Hash the password - See above for details:    
$password = password_hash($password, PASSWORD_BCRYPT, array("cost" => 15));	

// Store user's credentials, if form data is validated:
if(!$errMsg) {
   // Using prepared statements: 	   	  	
   $query = 'INSERT INTO users ( username, password ) VALUES ( :username, :password )';
   $stmt = $pdo->prepare($query);
   $result = $stmt->execute(array(':username' => $username, ':password' => $password));			 
   $errMsg = 'You have successfully registered to our great website!';   			     
}	   

}
?>

<?php echo (isset($errMsg)) ? $errMsg : '

Registration Page

'; ?>

Username:

Password:

[/php]

filename: login.php located in the root directory.
[php]<?php
// common.inc.php file contains required
// database connection initialization info:
require ‘includes/common.inc.php’;

// A nice password hashing library for PHP 5
// Find it here: https://github.com/ircmaxell/password_compat/blob/master/lib/password.php
// Read the Documentation for further help:
require ‘includes/password.inc.php’;

if (isset($_POST[‘action’]) && $_POST[‘action’] == ‘login’) {

 // This query retreives the user's information from the database using
 // their username.
$query = '
        SELECT
            id,
            username,
            password,
            DATE_FORMAT(date_added, "%e %M %Y") as date_added
        FROM users
        WHERE
            username = :username
        ';
	
// The parameter values
$query_params = array(
	':username' => $_POST['username']
);		


try
{
	// Execute the query against the database
	$stmt = $pdo->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)
{
	// Verify Stored Hashed Password:
	$result = password_verify($_POST['password'], $row['password']);
	
	if ($result) {
		$login_ok = true;	
	} else {
		$errMsg = '<p>Your credientials do not match!</p>';
	}
	  		
}

// If login is OK:
if ($login_ok) {
	
	// It's not wise to store the password in $_SESSION:
	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;
	
	// The following output is just to prove that it works:
	echo '<pre>';
	print_r($_SESSION);
	echo '</pre>';
	
	// Redirect the user to the private members-only page.
	//header("Location: admin.php");
	//die("Redirecting to: admin.php");		
}

}
/*

  • This was just to help people who are just getting started
  • learning how to program in the PHP Language. The PDO portion
  • is written in Object-Oriented Style, but this doesn’t mean
  • that you now know OOP or that you have to use it. It’s pretty
  • straight forward in my opinion. I have tested this out, but I make
  • no guarantees that it works 100 percent and it diffentely needs
  • updating/styling. However, that is up to you and besides it’s
  • a good way to learn PHP.
    */
    ?>
<?php echo (isset($errMsg)) ? $errMsg : '

Login Page:

'; ?>

Username:

Password:

[/php]

For further help and understanding about PHP go to php.net


#2

This is very well written code; very easy to understand what each piece of code does (if you know how to read PHP :wink: ).

Although the details behind how the code does what it’s supposed to do (which as you have said is solved by looking at the PHP.net man pages), or why you’ve chosen one particular php function or method over the other, are not so apparent.

I actually would have enjoyed reading why you wrote the code the way you did, which would by my only suggestion. However, forums are not exactly the best at this. :slight_smile:


#3

Performance might be an obvious thing to head for. However, why would you bother with reusability and extensibility? Because it sucks to have to deal with more than one codebase, and it sucks to have stuff magically break on you every time someone changes something. Believe me, I have been there - I have inherited codebases from people who did not write code with maintainability in mind (and was one of those before!), and frankly… the time that you save writing stuff quickly, you lose ten times over due to bugs the moment a slight mod is made.