PHP Login System Tutorial – Step By Step Guide!

โ€”

by

in

Previously, we learned how to create, read, update and delete database records on our PHP OOP CRUD tutorial.

Today we will build a secure and efficient PHP login system with admin features. By utilizing PHP sessions and MySQL database, we will walk you through creating a user-friendly login system that ensures the safety and protection of user data.

[adinserter block=”33″]

Overview

Login? Logout? Sign Up or Register? We see this functionality in almost every website we use today. Facebook, Gmail, and Twitter are just some examples.

If you want to become a web programmer, this is one of the most fundamental skills you need to learn. If you haven’t, today is a great day for you. We have some good news.

This tutorial will teach us how to build a simple PHP login system. This will help you create the web app of your dreams.

We will cover creating a PHP login form, registration form, customer section, admin section, list of registered users, logout, and more. You will learn helpful web programming techniques as well.

Prepare the database

Create a database

Open a new browser tab and go to PhpMyadmin. The address is http://localhost/phpmyadmin.

Create a database. The database name is: php_login_system

Run SQL for users’ table

In PhpMyAdmin, click the database name. Go to the SQL tab. Create the “users” table by running the following SQL statement.

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `firstname` varchar(32) NOT NULL,
  `lastname` varchar(32) NOT NULL,
  `email` varchar(64) NOT NULL,
  `contact_number` varchar(64) NOT NULL,
  `address` text NOT NULL,
  `password` varchar(512) NOT NULL,
  `access_level` varchar(16) NOT NULL,
  `access_code` text NOT NULL,
  `status` int(11) NOT NULL COMMENT '0=pending,1=confirmed',
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='admin and customer users';

Run SQL for users’ accounts

Go to PhpMyAdmin’s SQL tab again. Insert new records to the users table by running the following SQL statement.

INSERT INTO `users` (`id`, `firstname`, `lastname`, `email`, `contact_number`, `address`, `password`, `access_level`, `access_code`, `status`, `created`, `modified`) VALUES
(1, 'Mike', 'Dalisay', '[email protected]', '0999999999', 'Blk. 24 A, Lot 6, Ph. 3, Peace Village', '$2y$10$AUBptrm9sQF696zr8Hv31On3x4wqnTihdCLocZmGLbiDvyLpyokL.', 'Admin', '', 1, '0000-00-00 00:00:00', '2016-06-13 18:17:47'),
(2, 'Lauro', 'Abogne', '[email protected]', '08888888', 'Pasig City', '$2y$10$it4i11kRKrB19FfpPRWsRO5qtgrgajL7NnxOq180MsIhCKhAmSdDa', 'Customer', '', 1, '0000-00-00 00:00:00', '2015-03-24 07:00:21'),
(4, 'Darwin', 'Dalisay', '[email protected]', '9331868359', 'Blk 24 A Lot 6 Ph 3\r\nPeace Village, San Luis', '$2y$10$tLq9lTKDUt7EyTFhxL0QHuen/BgO9YQzFYTUyH50kJXLJ.ISO3HAO', 'Customer', 'ILXFBdMAbHVrJswNDnm231cziO8FZomn', 1, '2014-10-29 17:31:09', '2016-06-13 18:18:25'),
(7, 'Marisol Jane', 'Dalisay', '[email protected]', '09998765432', 'Blk. 24 A, Lot 6, Ph. 3, Peace Village', 'mariz', 'Customer', '', 1, '2015-02-25 09:35:52', '2015-03-24 07:00:21'),
(9, 'Marykris', 'De Leon', '[email protected]', '09194444444', 'Project 4, QC', '$2y$10$uUy7D5qmvaRYttLCx9wnU.WOD3/8URgOX7OBXHPpWyTDjU4ZteSEm', 'Customer', '', 1, '2015-02-27 14:28:46', '2015-03-24 06:51:03'),
(10, 'Merlin', 'Duckerberg', '[email protected]', '09991112333', 'Project 2, Quezon City', '$2y$10$VHY58eALB1QyYsP71RHD1ewmVxZZp.wLuhejyQrufvdy041arx1Kq', 'Admin', '', 1, '2015-03-18 06:45:28', '2015-03-24 07:00:21'),
(14, 'Charlon', 'Ignacio', '[email protected]', '09876543345', 'Tandang Sora, QC', '$2y$10$Fj6O1tPYI6UZRzJ9BNfFJuhURN9DnK5fQGHEsfol5LXRu.tCYYggu', 'Customer', '', 1, '2015-03-24 08:06:57', '2015-03-24 07:48:00'),
(15, 'Kobe Bro', 'Bryant', '[email protected]', '09898787674', 'Los Angeles, California', '$2y$10$fmanyjJxNfJ8O3p9jjUixu6EOHkGZrThtcd..TeNz2g.XZyCIuVpm', 'Customer', '', 1, '2015-03-26 11:28:01', '2015-03-26 03:39:52'),
(20, 'Tim', 'Duncan', '[email protected]', '9999999', 'San Antonio, Texas, USA', '$2y$10$9OSKHLhiDdBkJTmd3VLnQeNPCtyH1IvZmcHrz4khBMHdxc8PLX5G6', 'Customer', '0X4JwsRmdif8UyyIHSOUjhZz9tva3Czj', 1, '2016-05-26 01:25:59', '2016-05-25 17:25:59'),
(21, 'Tony', 'Parker', '[email protected]', '8888888', 'Blk 24 A Lot 6 Ph 3\r\nPeace Village, San Luis', '$2y$10$lBJfvLyl/X5PieaztTYADOxOQeZJCqETayF.O9ld17z3hcKSJwZae', 'Customer', 'THM3xkZzXeza5ISoTyPKl6oLpVa88tYl', 1, '2016-05-26 01:29:01', '2016-06-13 17:46:33');

Create database connection file

This class is used for connecting to a database.

I assume you are using XAMPP. Go to the root directory. In my case, it is the “C:\xampp\htdocs” directory.

We will create our project’s folder. Create “php-login-script-level-1” folder. Open that folder.

Create “config” folder and open it. Create database.php file. Place the following code.

<?php
// used to get mysql database connection
class Database{
	// specify your own database credentials
	private $host = "localhost";
	private $db_name = "php_login_system";
	private $username = "root";
	private $password = "";
	public $conn;
	// get the database connection
	public function getConnection(){
		$this->conn = null;
		try{
			$this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
		}catch(PDOException $exception){
			echo "Connection error: " . $exception->getMessage();
		}
		return $this->conn;
	}
}
?>

Prepare user object

This class is used for “user” object operations such as creating a user, deleting a user, and more. We will add several methods in this class along this tutorial.

We will just put the PHP class structure and necessary object properties.

Create an “objects” folder. Create “user.php” file. Place the following code.

<?php
// 'user' object
class User{
	// database connection and table name
	private $conn;
	private $table_name = "users";
	// object properties
	public $id;
	public $firstname;
	public $lastname;
	public $email;
	public $contact_number;
	public $address;
	public $password;
	public $access_level;
	public $access_code;
	public $status;
	public $created;
	public $modified;
	// constructor
	public function __construct($db){
		$this->conn = $db;
	}
}

Output

Congratulations! We just made the setup for our login system’s database. We do not have an output on a webpage yet. Continue the tutorial below to achieve more output.

Prepare basic settings

Create config file

Using a config file is great for your app. It is a central place where you can set common variables such as your app’s home URL, pagination settings, and more.

Open the “config” folder. Create “core.php” file. Place the following code.

<?php
// show error reporting
error_reporting(E_ALL);
// start php session
session_start();
// set your default time-zone
date_default_timezone_set('Asia/Manila');
// home page url
$home_url="http://localhost/php-login-script-level-1/";
// page given in URL parameter, default page is one
$page = isset($_GET['page']) ? $_GET['page'] : 1;
// set number of records per page
$records_per_page = 5;
// calculate for the query LIMIT clause
$from_record_num = ($records_per_page * $page) - $records_per_page;
?>

Create login checker file

A login checker file is used by our web app to identify if a user is logged in or not.

If a user is not logged in and he tries to access a page where a login is required, that user will be redirected to login page.

Also, if a logged in user tries to access a login page, he will be redirected to the dashboard because he is already logged in.

Back to root directory, create “login_checker.php” file. Place the following code.

<?php
// login checker for 'customer' access level
// if access level was not 'Admin', redirect him to login page
if(isset($_SESSION['access_level']) && $_SESSION['access_level']=="Admin"){
	header("Location: {$home_url}admin/index.php?action=logged_in_as_admin");
}
// if $require_login was set and value is 'true'
else if(isset($require_login) && $require_login==true){
	// if user not yet logged in, redirect to login page
	if(!isset($_SESSION['access_level'])){
		header("Location: {$home_url}login.php?action=please_login");
	}
}
// if it was the 'login' or 'register' or 'sign up' page but the customer was already logged in
else if(isset($page_title) && ($page_title=="Login" || $page_title=="Sign Up")){
	// if user not yet logged in, redirect to login page
	if(isset($_SESSION['access_level']) && $_SESSION['access_level']=="Customer"){
		header("Location: {$home_url}index.php?action=already_logged_in");
	}
}
else{
	// no problem, stay on current page
}
?>

Create .htaccess file

The .htaccess file is handy for making URLs pretty and easy to remember.

For example, the page on this URL “http://localhost/login.php” can be accessed using this URL “http://localhost/login“.

Create “.htaccess” file. Place the following code.

#Fix Rewrite
Options -Multiviews
# Mod Rewrite
Options +FollowSymLinks
RewriteEngine On
RewriteBase /php-login-script-level-1/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# used for php pages such as "yoursite.com/login.php" will become "yoursite.com/login/"
RewriteRule ^([a-z_]+)\/?$ $1.php [NC]

To make the .htaccess file work, ensure the mod_rewrite Apache module is enabled on your server. Learn more here and here.

Output

Congratulations! We just made the basic setup of our login system. We do not have an output on a webpage yet. Continue the tutorial below to achieve more output.

Create the template files

Template files are beneficial because it saves us a lot of time. How? When you use template files, you won’t have to repeat the common codes on each web page you are working on.

Examples of those common codes are the HTML tags on the ‘head’ and ‘foot’ of the web page. We will learn more about this concept as we go along this tutorial.

Create navigation bar

The navigation is where the user can click the home page link, logout button, and more.

Create “navigation.php” file. Place the following code.

<!-- navbar -->
<div class="navbar navbar-default navbar-static-top" role="navigation">
	<div class="container-fluid">
		<div class="navbar-header">
			<!-- to enable navigation dropdown when viewed in mobile device -->
			<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
			<span class="sr-only">Toggle navigation</span>
			<span class="icon-bar"></span>
			<span class="icon-bar"></span>
			<span class="icon-bar"></span>
			</button>
			<!-- Change "Your Site" to your site name -->
			<a class="navbar-brand" href="<?php echo $home_url; ?>">Your Site</a>
		</div>
		<div class="navbar-collapse collapse">
			<ul class="nav navbar-nav">
				<!-- link to the "Cart" page, highlight if current page is cart.php -->
				<li <?php echo $page_title=="Index" ? "class='active'" : ""; ?>>
					<a href="<?php echo $home_url; ?>">Home</a>
				</li>
			</ul>
			<?php
			// login and logout options will be here
			?>
		</div><!--/.nav-collapse -->
	</div>
</div>
<!-- /navbar -->

Show logout button

If the user was logged-in, the system will show them a logout button in the navigation bar.

Replace “// login and logout options will be here ” of the previous section with the following code.

// check if users / customer was logged in
// if user was logged in, show "Edit Profile", "Orders" and "Logout" options
if(isset($_SESSION['logged_in']) && $_SESSION['logged_in']==true && $_SESSION['access_level']=='Customer'){
?>
<ul class="nav navbar-nav navbar-right">
	<li <?php echo $page_title=="Edit Profile" ? "class='active'" : ""; ?>>
		<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
			<span class="glyphicon glyphicon-user" aria-hidden="true"></span>
			  <?php echo $_SESSION['firstname']; ?>
			  <span class="caret"></span>
		</a>
		<ul class="dropdown-menu" role="menu">
			<li><a href="<?php echo $home_url; ?>logout.php">Logout</a></li>
		</ul>
	</li>
</ul>
<?php
}
// show login and register options here

Show login and register buttons

If the user is not yet logged in, the system will show them the login and register buttons in the navigation bar.

Replace “// show login and register options here ” of the previous section with the following code.

// if user was not logged in, show the "login" and "register" options
else{
?>
<ul class="nav navbar-nav navbar-right">
	<li <?php echo $page_title=="Login" ? "class='active'" : ""; ?>>
		<a href="<?php echo $home_url; ?>login">
			<span class="glyphicon glyphicon-log-in"></span> Log In
		</a>
	</li>
	<li <?php echo $page_title=="Register" ? "class='active'" : ""; ?>>
		<a href="<?php echo $home_url; ?>register">
			<span class="glyphicon glyphicon-check"></span> Register
		</a>
	</li>
</ul>
<?php
}

Create page header

Our PHP pages contain almost the same HTML header code. We do not want to copy and paste the same HTML header code for different PHP files every time.

What we will do is to put them in this one file. It can be used by different PHP files using PHP include_once statement.

Create “layout_head.php” file. Place the following code.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
	<!-- set the page title, for seo purposes too -->
    <title><?php echo isset($page_title) ? strip_tags($page_title) : "Store Front"; ?></title>
    <!-- Bootstrap CSS -->
	<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" media="screen" />
	<!-- admin custom CSS -->
	<link href="<?php echo $home_url . "libs/css/customer.css" ?>" rel="stylesheet" />
</head>
<body>
	<!-- include the navigation bar -->
	<?php include_once 'navigation.php'; ?>
    <!-- container -->
    <div class="container">
		<?php
		// if given page title is 'Login', do not display the title
		if($page_title!="Login"){
		?>
		<div class='col-md-12'>
	        <div class="page-header">
	            <h1><?php echo isset($page_title) ? $page_title : "The Code of a Ninja"; ?></h1>
	        </div>
		</div>
		<?php
		}
		?>

The same concept with the page header file above, we do not want to copy and paste the same HTML footer code for different PHP files.

Create “layout_foot.php” file. Place the following code.

	</div>
	<!-- /container -->
<!-- jQuery library -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<!-- Bootstrap JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<!-- end HTML page -->
</body>
</html>

Customer section custom CSS

We will customize the web page for the customer using this CSS file. You can add your own CSS here as well.

Create “libs” folder. Create “css” folder. Create “customer.css” file. Place the following code.

.margin-bottom-1em{ margin-bottom:1em; }
.width-30-percent{ width:30%; }
.margin-1em-zero{ margin:1em 0; }
.width-30-percent{ width:30%; }
.width-70-percent{ width:70%; }
.margin-top-40{ margin-top:40px; }
.text-align-center{ text-align:center; }
div#blueimp-gallery div.modal{ overflow: visible; }
.photo-thumb{
    width:214px;
    height:214px;
    float:left;
    border: thin solid #d1d1d1;
    margin:0 1em 1em 0;
}
.form-signin{
    max-width: 330px;
    padding: 15px;
    margin: 0 auto;
}
.form-signin .form-control{
    position: relative;
    font-size: 16px;
    height: auto;
    padding: 10px;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}
.form-signin .form-control:focus{ z-index: 2; }
.form-signin input[type="text"]{
    margin-bottom: -1px;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
}
.form-signin input[type="password"]{
    margin-bottom: 10px;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}
.account-wall{
    margin-top: 40px;
    padding: 40px 0px 20px 0px;
    background-color: #ffffff;
    box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.16);
}
.login-title{
    color: #555;
    font-size: 22px;
    font-weight: 400;
    display: block;
}
.profile-img{
    width: 96px;
    height: 96px;
    margin: 0 auto 10px;
    display: block;
    -moz-border-radius: 50%;
    -webkit-border-radius: 50%;
    border-radius: 50%;
}
.select-img{
    border-radius: 50%;
    display: block;
    height: 75px;
    margin: 0 30px 10px;
    width: 75px;
    -moz-border-radius: 50%;
    -webkit-border-radius: 50%;
    border-radius: 50%;
}
.select-name{
    display: block;
    margin: 30px 10px 10px;
}
.logo-img{
    width: 96px;
    height: 96px;
    margin: 0 auto 10px;
    display: block;
    -moz-border-radius: 50%;
    -webkit-border-radius: 50%;
    border-radius: 50%;
}

Output

Congratulations! We just made the set up for our web pages. Our login page, register page, index page and other pages will look more beautiful now.

We do not have an output on a webpage yet. Continue the tutorial below to achieve the output of our login page.

Create a PHP login form

Include basic settings

This file is where the user will enter his email and password.

Create “login.php” file. Place the following code.

<?php
// core configuration
include_once "config/core.php";
// set page title
$page_title = "Login";
// include login checker
$require_login=false;
include_once "login_checker.php";
// default to false
$access_denied=false;
// post code will be here
// login form html will be here
?>

Login form HTML

Next, we will add the actual HTML form that contains the email field, password field and login button.

Replace “// login form html will be here” of the previous section with the following code.

// include page header HTML
include_once "layout_head.php";
echo "<div class='col-sm-6 col-md-4 col-md-offset-4'>";
	// alert messages will be here
	// actual HTML login form
	echo "<div class='account-wall'>";
		echo "<div id='my-tab-content' class='tab-content'>";
			echo "<div class='tab-pane active' id='login'>";
				echo "<img class='profile-img' src='images/login-icon.png'>";
				echo "<form class='form-signin' action='" . htmlspecialchars($_SERVER["PHP_SELF"]) . "' method='post'>";
					echo "<input type='text' name='email' class='form-control' placeholder='Email' required autofocus />";
					echo "<input type='password' name='password' class='form-control' placeholder='Password' required />";
					echo "<input type='submit' class='btn btn-lg btn-primary btn-block' value='Log In' />";
				echo "</form>";
			echo "</div>";
		echo "</div>";
	echo "</div>";
echo "</div>";
// footer HTML and JavaScript codes
include_once "layout_foot.php";

Login form output

Finally! We just made our first output. Open a new tab and go to http://localhost/php-login-script-level-1/login, you should see something like the following.

But our output does not work yet. Let’s continue coding below.

Add alert message

These alert messages are shown based on the given action parameter. The system action was generated as a result of the activity the user has taken.

For example, the user tries to access a page where a login is required. The system will generate the “please_login” action that will be used to show a “Please login to access that page.” message.

Replace “// alert messages will be here” of the previous section with the following code.

// get 'action' value in url parameter to display corresponding prompt messages
$action=isset($_GET['action']) ? $_GET['action'] : "";
// tell the user he is not yet logged in
if($action =='not_yet_logged_in'){
	echo "<div class='alert alert-danger margin-top-40' role='alert'>Please login.</div>";
}
// tell the user to login
else if($action=='please_login'){
	echo "<div class='alert alert-info'>
		<strong>Please login to access that page.</strong>
	</div>";
}
// tell the user email is verified
else if($action=='email_verified'){
	echo "<div class='alert alert-success'>
		<strong>Your email address have been validated.</strong>
	</div>";
}
// tell the user if access denied
if($access_denied){
	echo "<div class='alert alert-danger margin-top-40' role='alert'>
		Access Denied.<br /><br />
		Your username or password maybe incorrect
	</div>";
}

Alert message output

Here’s a sample output if we added the previous section’s code. The alert message below were seen if the user tries to access the customer index page http://localhost/php-login-script-level-1/ without logging in.

If user submitted the form

The following “if block” will execute a code when the user submitted the login form.

Find “// post code will be here” in “login.php” file. Replace it with the following code.

// if the login form was submitted
if($_POST){
	// email check will be here
}

Check if email exists

The code below will check if the submitted email address exists in the database. The $user->emailExists(); function will return true or false.

Replace “// email check will be here” of the previous section with the following code.

// include classes
include_once "config/database.php";
include_once "objects/user.php";
// get database connection
$database = new Database();
$db = $database->getConnection();
// initialize objects
$user = new User($db);
// check if email and password are in the database
$user->email=$_POST['email'];
// check if email exists, also get user details using this emailExists() method
$email_exists = $user->emailExists();
// login validation will be here

Add emailExists() method

The previous section will not work without the following method inside the user class. This method will read a user record from the database based on a given email address.

Open “objects” folder. Open “user.php” file. Place the following method in user class.

// check if given email exist in the database
function emailExists(){
	// query to check if email exists
	$query = "SELECT id, firstname, lastname, access_level, password, status
			FROM " . $this->table_name . "
			WHERE email = ?
			LIMIT 0,1";
	// prepare the query
	$stmt = $this->conn->prepare( $query );
	// sanitize
	$this->email=htmlspecialchars(strip_tags($this->email));
	// bind given email value
	$stmt->bindParam(1, $this->email);
	// execute the query
	$stmt->execute();
	// get number of rows
	$num = $stmt->rowCount();
	// if email exists, assign values to object properties for easy access and use for php sessions
	if($num>0){
		// get record details / values
		$row = $stmt->fetch(PDO::FETCH_ASSOC);
		// assign values to object properties
		$this->id = $row['id'];
		$this->firstname = $row['firstname'];
		$this->lastname = $row['lastname'];
		$this->access_level = $row['access_level'];
		$this->password = $row['password'];
		$this->status = $row['status'];
		// return true because email exists in the database
		return true;
	}
	// return false if email does not exist in the database
	return false;
}

Validate login credentials

The user will be allowed to login when $email_exists is true, the password matches and user status is equal to 1.

We used the built in PHP password_verify() function to match the submitted password with the hashed password in the database.

User status 1 means the user is verified. 0 means unverified. For this tutorial, we assume all users are verified.

Replace “// login validation will be here” of “login.php” with the following code.

// validate login
if ($email_exists && password_verify($_POST['password'], $user->password) && $user->status==1){
	// if it is, set the session value to true
	$_SESSION['logged_in'] = true;
	$_SESSION['user_id'] = $user->id;
	$_SESSION['access_level'] = $user->access_level;
	$_SESSION['firstname'] = htmlspecialchars($user->firstname, ENT_QUOTES, 'UTF-8') ;
	$_SESSION['lastname'] = $user->lastname;
	// if access level is 'Admin', redirect to admin section
	if($user->access_level=='Admin'){
		header("Location: {$home_url}admin/index.php?action=login_success");
	}
	// else, redirect only to 'Customer' section
	else{
		header("Location: {$home_url}index.php?action=login_success");
	}
}
// if username does not exist or password is wrong
else{
	$access_denied=true;
}

Customer’s index page

This file is shown when the user was successfully logged in.

Create “index.php” file. Place the following code.

<?php
// core configuration
include_once "config/core.php";
// set page title
$page_title="Index";
// include login checker
$require_login=true;
include_once "login_checker.php";
// include page header HTML
include_once 'layout_head.php';
echo "<div class='col-md-12'>";
	// to prevent undefined index notice
	$action = isset($_GET['action']) ? $_GET['action'] : "";
	// if login was successful
	if($action=='login_success'){
		echo "<div class='alert alert-info'>";
			echo "<strong>Hi " . $_SESSION['firstname'] . ", welcome back!</strong>";
		echo "</div>";
	}
	// if user is already logged in, shown when user tries to access the login page
	else if($action=='already_logged_in'){
		echo "<div class='alert alert-info'>";
			echo "<strong>You are already logged in.</strong>";
		echo "</div>";
	}
	// content once logged in
	echo "<div class='alert alert-info'>";
		echo "Content when logged in will be here. For example, your premium products or services.";
	echo "</div>";
echo "</div>";
// footer HTML and JavaScript codes
include 'layout_foot.php';
?>

Logout file

Here’s how to logout the logged in user.

Create “logout.php” file. Place the following code.

<?php
// core configuration
include_once "config/core.php";
// destroy session, it will remove ALL session settings
session_destroy();
//redirect to login page
header("Location: {$home_url}login.php");
?>

Output

The login form and logout should work at this point. When you try to login a customer account. Use the following login credentials.

Username: [email protected]
Password: darwin12qw!@QW

Once logged-in, you will see the following.

Create a PHP registration form

Create register page

The register page, also known as the “sign up” page, is where people will enter their information to be a registered user of the system.

Create “register.php” file. Place the following code.

<?php
// core configuration
include_once "config/core.php";
// set page title
$page_title = "Register";
// include login checker
include_once "login_checker.php";
// include classes
include_once 'config/database.php';
include_once 'objects/user.php';
include_once "libs/php/utils.php";
// include page header HTML
include_once "layout_head.php";
echo "<div class='col-md-12'>";
	// registration form HTML will be here
echo "</div>";
// include page footer HTML
include_once "layout_foot.php";
?>

Create Utils class

This class is used for accessing common or general methods used by our application. This is a good practice. We will add this here so that it can be used later in this tutorial as well.

Open “libs” folder. Create “php” folder and open it. Create “utils.php” file. Place the following code.

<?php
class Utils{
}
?>

Registration form HTML

The register.php file does not have the registration form. We will place the registration form using the code below.

Replace “// registration form HTML will be here” of the previous section with the following code.

// code when form was submitted will be here
?>
<form action='register.php' method='post' id='register'>
	<table class='table table-responsive'>
		<tr>
			<td class='width-30-percent'>Firstname</td>
			<td><input type='text' name='firstname' class='form-control' required value="<?php echo isset($_POST['firstname']) ? htmlspecialchars($_POST['firstname'], ENT_QUOTES) : "";  ?>" /></td>
		</tr>
		<tr>
			<td>Lastname</td>
			<td><input type='text' name='lastname' class='form-control' required value="<?php echo isset($_POST['lastname']) ? htmlspecialchars($_POST['lastname'], ENT_QUOTES) : "";  ?>" /></td>
		</tr>
		<tr>
			<td>Contact Number</td>
			<td><input type='text' name='contact_number' class='form-control' required value="<?php echo isset($_POST['contact_number']) ? htmlspecialchars($_POST['contact_number'], ENT_QUOTES) : "";  ?>" /></td>
		</tr>
		<tr>
			<td>Address</td>
			<td><textarea name='address' class='form-control' required><?php echo isset($_POST['address']) ? htmlspecialchars($_POST['address'], ENT_QUOTES) : "";  ?></textarea></td>
		</tr>
		<tr>
			<td>Email</td>
			<td><input type='email' name='email' class='form-control' required value="<?php echo isset($_POST['email']) ? htmlspecialchars($_POST['email'], ENT_QUOTES) : "";  ?>" /></td>
		</tr>
		<tr>
			<td>Password</td>
			<td><input type='password' name='password' class='form-control' required id='passwordInput'></td>
		</tr>
		<tr>
			<td></td>
			<td>
				<button type="submit" class="btn btn-primary">
					<span class="glyphicon glyphicon-plus"></span> Register
				</button>
			</td>
		</tr>
	</table>
</form>
<?php

If registration form was submitted

This is code where the system will catch the information submitted by the user. The code below will try to detect if the registration form was submitted. It also validates if the submitted email already exists or not.

Replace “// code when form was submitted will be here” of the previous section with the following code.

// if form was posted
if($_POST){
	// get database connection
	$database = new Database();
	$db = $database->getConnection();
	// initialize objects
	$user = new User($db);
	$utils = new Utils();
	// set user email to detect if it already exists
	$user->email=$_POST['email'];
	// check if email already exists
	if($user->emailExists()){
		echo "<div class='alert alert-danger'>";
			echo "The email you specified is already registered. Please try again or <a href='{$home_url}login'>login.</a>";
		echo "</div>";
	}
	else{
		// create user will be here
	}
}

Create new user

If the email does not exist, the program will create the user in the database. The code below will assign the submitted data to “user object” properties and then call the create() method.

Replace the comment “// create user will be here” of the previous section with the following code.

// set values to object properties
$user->firstname=$_POST['firstname'];
$user->lastname=$_POST['lastname'];
$user->contact_number=$_POST['contact_number'];
$user->address=$_POST['address'];
$user->password=$_POST['password'];
$user->access_level='Customer';
$user->status=1;
// create the user
if($user->create()){
	echo "<div class='alert alert-info'>";
		echo "Successfully registered. <a href='{$home_url}login'>Please login</a>.";
	echo "</div>";
	// empty posted values
	$_POST=array();
}else{
	echo "<div class='alert alert-danger' role='alert'>Unable to register. Please try again.</div>";
}

User object create() method

The create() method used in the previous section will not work without it in the user class. This method contains the INSERT query to the database, sanitizes the submitted data, binds the data and executes the query.

Open “objects” folder. Open “user.php” file. Add the following function inside the class.

// create new user record
function create(){
    // to get time stamp for 'created' field
    $this->created=date('Y-m-d H:i:s');
    // insert query
    $query = "INSERT INTO
                " . $this->table_name . "
            SET
				firstname = :firstname,
				lastname = :lastname,
				email = :email,
				contact_number = :contact_number,
				address = :address,
				password = :password,
				access_level = :access_level,
				status = :status,
				created = :created";
	// prepare the query
	$stmt = $this->conn->prepare($query);
	// sanitize
	$this->firstname=htmlspecialchars(strip_tags($this->firstname));
	$this->lastname=htmlspecialchars(strip_tags($this->lastname));
	$this->email=htmlspecialchars(strip_tags($this->email));
	$this->contact_number=htmlspecialchars(strip_tags($this->contact_number));
	$this->address=htmlspecialchars(strip_tags($this->address));
	$this->password=htmlspecialchars(strip_tags($this->password));
	$this->access_level=htmlspecialchars(strip_tags($this->access_level));
	$this->status=htmlspecialchars(strip_tags($this->status));
	// bind the values
	$stmt->bindParam(':firstname', $this->firstname);
	$stmt->bindParam(':lastname', $this->lastname);
	$stmt->bindParam(':email', $this->email);
	$stmt->bindParam(':contact_number', $this->contact_number);
	$stmt->bindParam(':address', $this->address);
	// hash the password before saving to database
	$password_hash = password_hash($this->password, PASSWORD_BCRYPT);
	$stmt->bindParam(':password', $password_hash);
	$stmt->bindParam(':access_level', $this->access_level);
	$stmt->bindParam(':status', $this->status);
	$stmt->bindParam(':created', $this->created);
	// execute the query, also check if query was successful
	if($stmt->execute()){
		return true;
	}else{
		$this->showError($stmt);
		return false;
	}
}

Add showError() method

The purpose of this method is to show additional details about an error.

public function showError($stmt){
	echo "<pre>";
		print_r($stmt->errorInfo());
	echo "</pre>";
}

Output

Run http://localhost/php-login-script-level-1/register, you should see an output the looks like the screenshot below. It will display appropriate alert message when the registration form was submitted.

Admin section

Create admin index page

Once the an admin user successfully logged in, this admin index page is where he will land.

Create “admin” folder. Create “index.php" file. Place the following code.

<?php
// core configuration
include_once "../config/core.php";
// check if logged in as admin
include_once "login_checker.php";
// set page title
$page_title="Admin Index";
// include page header HTML
include 'layout_head.php';
	echo "<div class='col-md-12'>";
		// get parameter values, and to prevent undefined index notice
		$action = isset($_GET['action']) ? $_GET['action'] : "";
		// tell the user he's already logged in
		if($action=='already_logged_in'){
			echo "<div class='alert alert-info'>";
				echo "<strong>You</strong> are already logged in.";
			echo "</div>";
		}
		else if($action=='logged_in_as_admin'){
			echo "<div class='alert alert-info'>";
				echo "<strong>You</strong> are logged in as admin.";
			echo "</div>";
		}
		echo "<div class='alert alert-info'>";
			echo "Contents of your admin section will be here.";
		echo "</div>";
	echo "</div>";
// include page footer HTML
include_once 'layout_foot.php';
?>

Create navigation bar

The admin section has its own navigation bar. The reason is the admin menu can be very different from the user menu.

Open “admin” folder. Create “navigation.php” file. Place the following code.

<!-- navbar -->
<div class="navbar navbar-default navbar-static-top" role="navigation">
	<div class="container-fluid">
		<div class="navbar-header">
			<!-- to enable navigation dropdown when viewed in mobile device -->
			<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
			<span class="sr-only">Toggle navigation</span>
			<span class="icon-bar"></span>
			<span class="icon-bar"></span>
			<span class="icon-bar"></span>
			</button>
			<!-- Change "Site Admin" to your site name -->
			<a class="navbar-brand" href="<?php echo $home_url; ?>admin/index.php">Admin</a>
		</div>
		<div class="navbar-collapse collapse">
			<ul class="nav navbar-nav">
				<!-- highlight for order related pages -->
				<li <?php echo $page_title=="Admin Index" ? "class='active'" : ""; ?>>
					<a href="<?php echo $home_url; ?>admin/index.php">Home</a>
				</li>
				<!-- highlight for user related pages -->
				<li <?php
						echo $page_title=="Users" ? "class='active'" : ""; ?> >
					<a href="<?php echo $home_url; ?>admin/read_users.php">Users</a>
				</li>
			</ul>
			<!-- options in the upper right corner of the page -->
			<ul class="nav navbar-nav navbar-right">
				<li>
					<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
						<span class="glyphicon glyphicon-user" aria-hidden="true"></span>
						  <?php echo $_SESSION['firstname']; ?>
						  <span class="caret"></span>
					</a>
					<ul class="dropdown-menu" role="menu">
						<!-- log out user -->
						<li><a href="<?php echo $home_url; ?>logout.php">Logout</a></li>
					</ul>
				</li>
			</ul>
		</div><!--/.nav-collapse -->
	</div>
</div>
<!-- /navbar -->

Create page header

This page header file is for admin section only. This is where the navigation bar of the previous section was used.

Open “admin” folder. Create “layout_head.php” file. Place the following code.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title><?php echo isset($page_title) ? strip_tags($page_title) : "Store Admin"; ?></title>
    <!-- Bootstrap CSS -->
	<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" media="screen" />
	<!-- admin custom CSS -->
	<link href="<?php echo $home_url . "libs/css/admin.css" ?>" rel="stylesheet" />
</head>
<body>
	<?php
	// include top navigation bar
	include_once "navigation.php";
	?>
    <!-- container -->
    <div class="container">
		<!-- display page title -->
        <div class="col-md-12">
            <div class="page-header">
                <h1><?php echo isset($page_title) ? $page_title : "The Code of a Ninja"; ?></h1>
            </div>
        </div>

This page footer file is for admin section only as well.

Open “admin” folder. Create “layout_foot.php” file. Place the following code.

	</div>
	<!-- /container -->
<!-- jQuery library -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<!-- Bootstrap JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<!-- end the HTML page -->
</body>
</html>

Admin section custom CSS

There are differences in style in the admin section as well. For this reason, we need a different custom.css file.

Open “libs” folder. Open “css” folder. Create “admin.css” file. Place the following code.

.right-button-margin{
    margin: 0 0 1em 0;
    overflow: hidden;
}
.thumb-image {
    float:left;
    width: 150px;
    height: 150px;
    margin:.6em 1.2em 0 0;
    position:relative;
}
.thumb-image .delete-image {
    position:absolute;
    top:-10px;
    right:-10px;
}
.thumb-wrapper{
    border:thin solid #999;
    float:left;
    width: 150px;
    height: 150px;
    margin:.6em 1.2em 0 0;
    position:relative;
}
.width-30-percent{ width:30%; }
.margin-left-1em{ margin:0 1em 0 0; }
.width-20-em{ width: 20em; }
.width-13-em{ width: 13em; }
.margin-zero{ margin:0; }
.padding-zero{ padding:0; }
.btn-margin-right{ margin-right:.5em; }
.margin-bottom-1em{ margin-bottom:1em; }
.left-margin{ margin:0 .5em 0 0; }
.delete-image img{ width:25px; }
.thumb-wrapper img{ width:130px; height:130px; margin:10px; }
#html-btn { display:none; }
.delete-pdf{ margin-left:.5em; }
.delete-pdf img{ width:20px; cursor:pointer; }
.pdf-item{ padding:0 0 1em 0; }

Create login checker file

We choose to use a different login checker for the admin section. This is because there can be different redirects required by the admin section.

Open “admin” folder. Create “login_checker.php” file. Place the following code.

<?php
// login checker for 'admin' access level
// if the session value is empty, he is not yet logged in, redirect him to login page
if(empty($_SESSION['logged_in'])){
    header("Location: {$home_url}login.php?action=not_yet_logged_in");
}
// if access level was not 'Admin', redirect him to login page
else if($_SESSION['access_level']!="Admin"){
	header("Location: {$home_url}login.php?action=not_admin");
}
else{
	// no problem, stay on current page
}
?>

Output

Use the following login credentials to login an admin account.

Username: [email protected]
Password: ninja12qw!@QW

Once logged-in, you will see the following.

Show registered users

Create “read users” file

This file will is what was accessed in the browser to show the list of registered users.

Create read_users.php file. Place the following code.

<?php
// core configuration
include_once "../config/core.php";
// check if logged in as admin
include_once "login_checker.php";
// include classes
include_once '../config/database.php';
include_once '../objects/user.php';
// get database connection
$database = new Database();
$db = $database->getConnection();
// initialize objects
$user = new User($db);
// set page title
$page_title = "Users";
// include page header HTML
include_once "layout_head.php";
echo "<div class='col-md-12'>";
    // read all users from the database
    $stmt = $user->readAll($from_record_num, $records_per_page);
    // count retrieved users
    $num = $stmt->rowCount();
    // to identify page for paging
    $page_url="read_users.php?";
    // include products table HTML template
    include_once "read_users_template.php";
echo "</div>";
// include page footer HTML
include_once "layout_foot.php";
?>

Add readAll() method in user object

This method is needed by the previous section to retrieved records from the database.

Open “objects” folder. Open “user.php” file. Place the following code inside the class.

// read all user records
function readAll($from_record_num, $records_per_page){
	// query to read all user records, with limit clause for pagination
	$query = "SELECT
				id,
				firstname,
				lastname,
				email,
				contact_number,
				access_level,
				created
			FROM " . $this->table_name . "
			ORDER BY id DESC
			LIMIT ?, ?";
	// prepare query statement
	$stmt = $this->conn->prepare( $query );
	// bind limit clause variables
	$stmt->bindParam(1, $from_record_num, PDO::PARAM_INT);
	$stmt->bindParam(2, $records_per_page, PDO::PARAM_INT);
	// execute query
	$stmt->execute();
	// return values
	return $stmt;
}

Create “read users” template file

The HTML table is where the records are placed. We used a template file so that we can use the same layout for the search feature.

Create read_users_template.php file. Place the following code.

<?php
// display the table if the number of users retrieved was greater than zero
if($num>0){
	echo "<table class='table table-hover table-responsive table-bordered'>";
	// table headers
	echo "<tr>";
		echo "<th>Firstname</th>";
		echo "<th>Lastname</th>";
		echo "<th>Email</th>";
		echo "<th>Contact Number</th>";
		echo "<th>Access Level</th>";
	echo "</tr>";
	// loop through the user records
        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)){
		extract($row);
		// display user details
		echo "<tr>";
			echo "<td>{$firstname}</td>";
			echo "<td>{$lastname}</td>";
			echo "<td>{$email}</td>";
			echo "<td>{$contact_number}</td>";
			echo "<td>{$access_level}</td>";
		echo "</tr>";
        }
	echo "</table>";
	$page_url="read_users.php?";
	$total_rows = $user->countAll();
	// actual paging buttons
	include_once 'paging.php';
}
// tell the user there are no selfies
else{
	echo "<div class='alert alert-danger'>
		<strong>No users found.</strong>
	</div>";
}
?>

Add countAll() method in user object

The data returned by the countAll() method is used for calculating the correct pagination buttons.

Open “objects” folder. Open “user.php” file. Place the following code inside the class.

// used for paging users
public function countAll(){
	// query to select all user records
	$query = "SELECT id FROM " . $this->table_name . "";
	// prepare query statement
	$stmt = $this->conn->prepare($query);
	// execute query
	$stmt->execute();
	// get number of rows
	$num = $stmt->rowCount();
	// return row count
	return $num;
}

Paginate list of users

This pagination file renders the clickable page buttons. It can be used for different objects as well, not just users.

Create paging.php file. Place the following code.

<?php
echo "<ul class=\"pagination margin-zero\">";
// button for first page
if($page>1){
    echo "<li><a href='{$page_url}' title='Go to the first page.'>";
        echo "First Page";
    echo "</a></li>";
}
// calculate total number of pages
$total_pages = ceil($total_rows / $records_per_page);
// range of links to show
$range = 2;
// display links to 'range of pages' around 'current page'
$initial_num = $page - $range;
$condition_limit_num = ($page + $range)  + 1;
for ($x=$initial_num; $x<$condition_limit_num; $x++) {
    // be sure '$x is greater than 0' AND 'less than or equal to the $total_pages'
    if (($x > 0) && ($x <= $total_pages)) {
        // current page
        if ($x == $page) {
            echo "<li class='active'><a href=\"#\">$x <span class=\"sr-only\">(current)</span></a></li>";
        }
        // not current page
        else {
            echo "<li><a href='{$page_url}page=$x'>$x</a></li>";
        }
    }
}
// button for last page
if($page<$total_pages){
    echo "<li><a href='" .$page_url . "page={$total_pages}' title='Last page is {$total_pages}.'>";
        echo "Last Page";
    echo "</a></li>";
}
echo "</ul>";
?>

Output

Login as admin and go to http://localhost/php-login-script-level-1/admin/read_users.php, you should see an output like the following.

How to validate email address?

This is a bonus section. This feature is not part of LEVEL 1 source code.

This feature needs a remote server where PHP mail function works. It does not work in localhost.

Change account status

If the user status is equal to 1, it means the email account is valid.

Since we want the user to validate his own email address, we will make it invalid (status equals to 0) the first time he register an account to our system.

Open register.php file and find:

Change the it to:

$user->status=1;

Add access code field

Access code is a unique string of characters that is needed for email validation process. Still in register.php, place the following code under the code of the previous section.

// access code for email verification
$access_code=$utils->getToken();
$user->access_code=$access_code;

Add getToken() method

This method will generate a unique string of characters for the access code field of the previous section.

Open “libs/php/utils.php” file and place the following code inside the class.

function getToken($length=32){
	$token = "";
	$codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	$codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
	$codeAlphabet.= "0123456789";
	for($i=0;$i<$length;$i++){
		$token .= $codeAlphabet[$this->crypto_rand_secure(0,strlen($codeAlphabet))];
	}
	return $token;
}
function crypto_rand_secure($min, $max) {
	$range = $max - $min;
	if ($range < 0) return $min; // not so random...
	$log = log($range, 2);
	$bytes = (int) ($log / 8) + 1; // length in bytes
	$bits = (int) $log + 1; // length in bits
	$filter = (int) (1 << $bits) - 1; // set all lower bits to 1
	do {
		$rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
		$rnd = $rnd & $filter; // discard irrelevant bits
	} while ($rnd >= $range);
	return $min + $rnd;
}

Change create() method

Since we added a new field, we need to change the create() method in the User class.

Open objects/user.php file. Replace the create() method with the new one below.

// create new user record
function create(){
    // to get time stamp for 'created' field
    $this->created=date('Y-m-d H:i:s');
    // insert query
    $query = "INSERT INTO " . $this->table_name . "
            SET
		firstname = :firstname,
		lastname = :lastname,
		email = :email,
		contact_number = :contact_number,
		address = :address,
		password = :password,
		access_level = :access_level,
                access_code = :access_code,
		status = :status,
		created = :created";
	// prepare the query
	$stmt = $this->conn->prepare($query);
	// sanitize
	$this->firstname=htmlspecialchars(strip_tags($this->firstname));
	$this->lastname=htmlspecialchars(strip_tags($this->lastname));
	$this->email=htmlspecialchars(strip_tags($this->email));
	$this->contact_number=htmlspecialchars(strip_tags($this->contact_number));
	$this->address=htmlspecialchars(strip_tags($this->address));
	$this->password=htmlspecialchars(strip_tags($this->password));
	$this->access_level=htmlspecialchars(strip_tags($this->access_level));
	$this->access_code=htmlspecialchars(strip_tags($this->access_code));
	$this->status=htmlspecialchars(strip_tags($this->status));
	// bind the values
	$stmt->bindParam(':firstname', $this->firstname);
	$stmt->bindParam(':lastname', $this->lastname);
	$stmt->bindParam(':email', $this->email);
	$stmt->bindParam(':contact_number', $this->contact_number);
	$stmt->bindParam(':address', $this->address);
	// hash the password before saving to database
	$password_hash = password_hash($this->password, PASSWORD_BCRYPT);
	$stmt->bindParam(':password', $password_hash);
	$stmt->bindParam(':access_level', $this->access_level);
	$stmt->bindParam(':access_code', $this->access_code);
	$stmt->bindParam(':status', $this->status);
	$stmt->bindParam(':created', $this->created);
	// execute the query, also check if query was successful
	if($stmt->execute()){
		return true;
	}else{
		$this->showError($stmt);
		return false;
	}
}

Add access_code property

The previous section will not work without this new “access_code” property. Open on objects/user.php file, find this line:

public $access_level;

And add the following code under it.

public $access_code;

11.6 Send verification email

Instead of just displaying “Successfully registered.” message, we will send a verification link to user’s email address.

This means we will use a new function and message saying “Verification link was sent to your email. Click that link to login.”.

Open register.php file and find the following “if-else” statement.

// create the user
if($user->create()){
	echo "<div class='alert alert-info'>";
		echo "Successfully registered. <a href='{$home_url}login'>Please login</a>.";
	echo "</div>";
	// empty posted values
	$_POST=array();
}else{
	echo "<div class='alert alert-danger' role='alert'>Unable to register. Please try again.</div>";
}

Replace it with the following code.

// create the user
if($user->create()){
	// send confimation email
	$send_to_email=$_POST['email'];
	$body="Hi {$send_to_email}.<br /><br />";
	$body.="Please click the following link to verify your email and login: {$home_url}verify/?access_code={$access_code}";
	$subject="Verification Email";
	if($utils->sendEmailViaPhpMail($send_to_email, $subject, $body)){
		echo "<div class='alert alert-success'>
			Verification link was sent to your email. Click that link to login.
		</div>";
	}
	else{
		echo "<div class='alert alert-danger'>
			User was created but unable to send verification email. Please contact admin.
		</div>";
	}
	// empty posted values
	$_POST=array();
}else{
	echo "<div class='alert alert-danger' role='alert'>Unable to register. Please try again.</div>";
}

Add sendEmailViaPhpMail() method

This method will help send the email with verification link to the email address submitted by the user. It uses the built-in PHP mail() function.

Open “libs/php/utils.php” file. Place the following code inside the class.

// send email using built in php mail function
public function sendEmailViaPhpMail($send_to_email, $subject, $body){
	$from_name="Your Name";
	$from_email="[email protected]";
	$headers  = "MIME-Version: 1.0\r\n";
	$headers .= "Content-type: text/html; charset=iso-8859-1\r\n";
	$headers .= "From: {$from_name} <{$from_email}> \n";
	if(mail($send_to_email, $subject, $body, $headers)){
		return true;
	}
	return false;
}

Change the $from_name and $from_email variable values to your own.

Output

Try to register a new account. After submitting the form, you will see a message box like the following. Follow what the message says to validate the email address.

The verification email that the user will receive looks like the following. You can always change the copy of this message using the $subject and $body variables.

Create verification file

Once the user clicked the verification link, the system will change the status from 0 to 1.

He will be redirected to the login page with a confirmation message as well.

Create verify.php file in your project’s main directory. Place the following code.

<?php
// core configuration
include_once "config/core.php";
// include classes
include_once 'config/database.php';
include_once 'objects/user.php';
// get database connection
$database = new Database();
$db = $database->getConnection();
// initialize objects
$user = new User($db);
// set access code
$user->access_code=isset($_GET['access_code']) ? $_GET['access_code'] : "";
// verify if access code exists
if(!$user->accessCodeExists()){
	die("ERROR: Access code not found.");
}
// redirect to login
else{
	// update status
	$user->status=1;
	$user->updateStatusByAccessCode();
	// and the redirect
	header("Location: {$home_url}login.php?action=email_verified");
}
?>

Add accessCodeExists() method

This method will check the database if an access code already exists. It will return true if it found something, else, it will return false.

Open โ€œobjects/user.phpโ€ file. Add the following code inside the class.

// check if given access_code exist in the database
function accessCodeExists(){
	// query to check if access_code exists
	$query = "SELECT id
			FROM " . $this->table_name . "
			WHERE access_code = ?
			LIMIT 0,1";
	// prepare the query
	$stmt = $this->conn->prepare( $query );
	// sanitize
	$this->access_code=htmlspecialchars(strip_tags($this->access_code));
	// bind given access_code value
	$stmt->bindParam(1, $this->access_code);
	// execute the query
	$stmt->execute();
	// get number of rows
	$num = $stmt->rowCount();
	// if access_code exists
	if($num>0){
		// return true because access_code exists in the database
		return true;
	}
	// return false if access_code does not exist in the database
	return false;
}

Add updateStatusByAccessCode() method

Open “objects” folder. Open “user.php” file. Add the following method inside the class.

// used in email verification feature
function updateStatusByAccessCode(){
	// update query
	$query = "UPDATE " . $this->table_name . "
			SET status = :status
			WHERE access_code = :access_code";
	// prepare the query
	$stmt = $this->conn->prepare($query);
	// sanitize
	$this->status=htmlspecialchars(strip_tags($this->status));
	$this->access_code=htmlspecialchars(strip_tags($this->access_code));
	// bind the values from the form
	$stmt->bindParam(':status', $this->status);
	$stmt->bindParam(':access_code', $this->access_code);
	// execute the query
	if($stmt->execute()){
		return true;
	}
	return false;
}

Output

The previous code will redirect the user to the following page.

PHP forgot password

This is a bonus section. This feature is not part of LEVEL 1 source code.

This feature needs a remote server where PHP mail() function works. It does not work in localhost.

Open login.php file and find the login button. Look for a line that looks like the following code.

echo "<input type='submit' class='btn btn-lg btn-primary btn-block' value='Log In' />";

Once you found it, place the following code under it.

echo "<div class='margin-1em-zero text-align-center'>
	<a href='{$home_url}forgot_password'>Forgot password?</a>
</div>";

Login page new output

The login page will have the “Forgot password?” link after doing the code in the previous section.

Create “forgot password” page

This is the page where the user lands when he clicked the “Forgot password?” link on the login page.

Create forgot_password.php file in your project’s main directory. Place the following code.

<?php
// core configuration
include_once "config/core.php";
// set page title
$page_title = "Forgot Password";
// include login checker
include_once "login_checker.php";
// include classes
include_once "config/database.php";
include_once 'objects/user.php';
include_once "libs/php/utils.php";
// get database connection
$database = new Database();
$db = $database->getConnection();
// initialize objects
$user = new User($db);
$utils = new Utils();
// include page header HTML
include_once "layout_head.php";
// post code will be here
// show reset password HTML form
echo "<div class='col-md-4'></div>";
echo "<div class='col-md-4'>";
	echo "<div class='account-wall'>
		<div id='my-tab-content' class='tab-content'>
			<div class='tab-pane active' id='login'>
				<img class='profile-img' src='images/login-icon.png'>
				<form class='form-signin' action='" . htmlspecialchars($_SERVER["PHP_SELF"]) . "' method='post'>
					<input type='email' name='email' class='form-control' placeholder='Your email' required autofocus>
					<input type='submit' class='btn btn-lg btn-primary btn-block' value='Send Reset Link' style='margin-top:1em;' />
				</form>
			</div>
		</div>
	</div>";
echo "</div>";
echo "<div class='col-md-4'></div>";
// footer HTML and JavaScript codes
include_once "layout_foot.php";
?>

Forgot password page output

After doing the code above, run forget_password.php on your browser. You will see the following output.

Add post code

Once the user entered his email address and submitted the form, this code will be executed.

Replace “// post code will be here” with the following code.

// if the login form was submitted
if($_POST){
	echo "<div class='col-sm-12'>";
		// check if username and password are in the database
		$user->email=$_POST['email'];
		if($user->emailExists()){
			// update access code for user
			$access_code=$utils->getToken();
			$user->access_code=$access_code;
			if($user->updateAccessCode()){
				// send reset link
				$body="Hi there.<br /><br />";
				$body.="Please click the following link to reset your password: {$home_url}reset_password/?access_code={$access_code}";
				$subject="Reset Password";
				$send_to_email=$_POST['email'];
				if($utils->sendEmailViaPhpMail($send_to_email, $subject, $body)){
					echo "<div class='alert alert-info'>
							Password reset link was sent to your email.
							Click that link to reset your password.
						</div>";
				}
				// message if unable to send email for password reset link
				else{ echo "<div class='alert alert-danger'>ERROR: Unable to send reset link.</div>"; }
			}
			// message if unable to update access code
			else{ echo "<div class='alert alert-danger'>ERROR: Unable to update access code.</div>"; }
		}
		// message if email does not exist
		else{ echo "<div class='alert alert-danger'>Your email cannot be found.</div>"; }
	echo "</div>";
}

Add updateAccessCode() method

Open “objects” folder. Open “user.php” file. Add the following method inside the class.

// used in forgot password feature
function updateAccessCode(){
	// update query
	$query = "UPDATE
				" . $this->table_name . "
			SET
				access_code = :access_code
			WHERE
				email = :email";
	// prepare the query
	$stmt = $this->conn->prepare($query);
	// sanitize
	$this->access_code=htmlspecialchars(strip_tags($this->access_code));
	$this->email=htmlspecialchars(strip_tags($this->email));
	// bind the values from the form
	$stmt->bindParam(':access_code', $this->access_code);
	$stmt->bindParam(':email', $this->email);
	// execute the query
	if($stmt->execute()){
		return true;
	}
	return false;
}

Output

Here’s a sample email sent after the user submitted his email address in using the “forgot password” form.

PHP reset password form

The “reset password” form is shown when the user clicked the link sent by the system after the “forgot password” request.

Create “reset password” page

If the use clicks the link on the email our system sent, he will land to this page where he will change his password.

Create reset_password.php file. Place the following code.

<?php
// core configuration
include_once "config/core.php";
// set page title
$page_title = "Reset Password";
// include login checker
include_once "login_checker.php";
// include classes
include_once "config/database.php";
include_once "objects/user.php";
// get database connection
$database = new Database();
$db = $database->getConnection();
// initialize objects
$user = new User($db);
// include page header HTML
include_once "layout_head.php";
echo "<div class='col-sm-12'>";
	// check acess code will be here
echo "</div>";
// include page footer HTML
include_once "layout_foot.php";
?>

Check access code

We need to check the access code to verify if the request came from the email generated by our system.

Replace the comment “// check acess code will be here” of the previous section with the following code.

// get given access code
$access_code=isset($_GET['access_code']) ? $_GET['access_code'] : die("Access code not found.");
// check if access code exists
$user->access_code=$access_code;
if(!$user->accessCodeExists()){
	die('Access code not found.');
}
else{
	// reset password form will be here
}

Add “reset password” form

If the access code exists, the page will render a form where the user can enter his new password.

Once the user submitted a new password, the database will be updated with this new password data.

Replace the comment “// reset password form will be here” of the previous section with the following code.

// post code will be here
echo "<form action='" . htmlspecialchars($_SERVER["PHP_SELF"]) . "?access_code={$access_code}' method='post'>
    <table class='table table-hover table-responsive table-bordered'>
		<tr>
            <td>Password</td>
            <td><input type='password' name='password' class='form-control' required></td>
        </tr>
        <tr>
            <td></td>
            <td><button type='submit' class='btn btn-primary'>Reset Password</button></td>
        </tr>
    </table>
</form>";

Add post code

Replace the comment “// post code will be here” of the previous section with the following code.

// if form was posted
if($_POST){
	// set values to object properties
	$user->password=$_POST['password'];
	// reset password
	if($user->updatePassword()){
		echo "<div class='alert alert-info'>Password was reset. Please <a href='{$home_url}login'>login.</a></div>";
	}
	else{
		echo "<div class='alert alert-danger'>Unable to reset password.</div>";
	}
}

Add updatePassword() method

This method will help update the password in the database. The previous section won’t work without this method in the User class.

Open “objects/user.php” file. Add the following code inside the class.

// used in forgot password feature
function updatePassword(){
	// update query
	$query = "UPDATE " . $this->table_name . "
			SET password = :password
			WHERE access_code = :access_code";
	// prepare the query
	$stmt = $this->conn->prepare($query);
	// sanitize
	$this->password=htmlspecialchars(strip_tags($this->password));
	$this->access_code=htmlspecialchars(strip_tags($this->access_code));
	// bind the values from the form
	$password_hash = password_hash($this->password, PASSWORD_BCRYPT);
	$stmt->bindParam(':password', $password_hash);
	$stmt->bindParam(':access_code', $this->access_code);
	// execute the query
	if($stmt->execute()){
		return true;
	}
	return false;
}

Output

To run reset_password.php file, click the link sent to your email address via “forgot password” form.

Download source codes

Choose the source code with features that you need or want to learn.

FEATURESBASICPRO
PHP login form with email and passwordโœ”โœ”
PHP Sessions are used to identify logged-in and logged-out users.โœ”โœ”
Hashed password stored in the databaseโœ”โœ”
PHP registration formโœ”โœ”
Redirection to the login page if the user is not yet logged inโœ”โœ”
Customer access to the index page when logged inโœ”โœ”
Customer logoutโœ”โœ”
Admin users list paginationโœ”โœ”
Admin logoutโœ”โœ”
Require login in admin index and users list pageโœ”โœ”
Bootstrap-enabled user interfaceโœ”โœ”
Password and confirm password fieldsโœ”
Check if the password matchesโœ”
Sending of verification link to emailโœ”
Validation page or email linkโœ”
Check if the password is strong enoughโœ”
Forgot password pageโœ”
Password reset link sent to the emailโœ”
PHP password reset formโœ”
Customer access to edit profile page when logged inโœ”
Customer change password pageโœ”
Customer password and confirm password field when editing profileโœ”
Customer logoutโœ”
Admin creates, reads, update, and deletes usersโœ”
Admin search user by email addressโœ”
Admin edit profileโœ”
Admin change password pageโœ”
Admin can change user passwordsโœ”
Admin can manually change the status of users (pending or approved)โœ”
Require login in admin index page, edit profile page and users CRUD pages.โœ”
Use the buttons below to download. โ†“BASICPRO

How To Run The Source Code?

After you download the source code, extract the zip file. There you will see a README.txt file. Read the exact instructions there about how to run the source code.

Whatโ€™s Next?

For our next tutorial, we will learn how to create a simple PHP shopping cart that uses a MySQL database.

Click here to proceed to the next tutorial: PHP Shopping Cart Tutorial โ€“ Step By Step Guide!

[adinserter block=”3″]


Comments

58 responses to “PHP Login System Tutorial – Step By Step Guide!”

  1. ccornutt Avatar
    ccornutt

    I’d suggest that rather than just having a comment saying “password should be encrypted” you describe this process and show how to validate the password (I’d hope you mean “salt and hash the password” not really “encrypt).

    I could see a lot of people hard-coding the username and password into the script just copy and pasting your example and that’s teaching a very bad practice. Another option is just hashing the password with a salt and writing it to a text file – it’s not ideal, but it’s simpler than a database.

    If you’re looking for a good tool for password hashing, check out this project: https://github.com/ircmaxell/password_compat (or, if you’re on PHP 5.5+ you can just use the password_hash() function natively).

    1. Thanks for pointing that out, I agree with you, but I have a different perspective. If people are really going to create a production-ready login code, no doubt they will search and find out about what I said “password should be encrypted”. My goal in this post is to show people the simplest way to understand how a PHP login script usually works and give them a small start. Thanks again for your contribution to this post! I updated the post a bit.

  2. Guest Avatar
    Guest

    I downloaded the code and tried to run it on a WAMP server. When I do, and go to login.php I get an error that ‘loged_in’ is an undefined index. Same with ‘action’ when looking in the $_GET array. Is there something I’m missing? I’m new to PHP and web development and appreciate the posts here very much.

  3. Philip Allen Avatar
    Philip Allen

    I tried running this code on a WAMP server so that I could experiment with it myself. When I did I got errors saying that ‘logged_in’ and ‘action’ array indexes didn’t exist. Am I missing something that would have created those in the appropriate arrays?

    1. Hi Philip, thanks for dropping by! You can check if the session or get variable was set and set it to another variable, like this:

      $logged_in = isset($_SESSION[‘logged_in’]) ? $_SESSION[‘logged_in’] : “”;

      and then use the $logged_in variable for the if statement.

      OR

      You can go to wamp > php > php settings

      and then uncheck either expose php, track errors, or display errors. i forgot which is which. please let us know if you tried.

      1. Philip Allen Avatar
        Philip Allen

        Thanks, and sorry for the double post. I did find that your code does function fine and the errors are invisible to the user when display errors is turned off in the WAMP server. I will try your first option to check/initialize the session variables and see if that takes care of the problem.

  4. Thank you so much for the tip but I got error like this using exactly your files in my server:
    Warning: Cannot modify header information – headers already sent by (output started at /test/login.php:22) in /test/login.php on line 43

    1. Hi @disqus_265IRVpnsZ, you must make sure the first line of code in you php file is the session_start();

  5. Thanks a lot,the tutorials are just wonderful. Learned alot from them.

    1. ninjazhai Avatar
      ninjazhai

      Hey @Dan, thanks for the kind words! You’re welcome!

  6. IMS Formation Avatar
    IMS Formation

    please where is the logout file ??

    1. Hi @imsformation, the logout file is in section 7.11 above. Thanks for letting me know that it’s missing!

      1. IMS Formation Avatar
        IMS Formation

        not at all. plz i want a help about shopping cart with session it doesn’t work for me :where placing 6.12 bloc of

        Make โ€œadd to cartโ€ button work????

        1. You have to place that code inside layout_foot.php file. I updated that section as well. Thanks.

  7. Julian Libor Avatar
    Julian Libor

    Hi,

    thanks a lot for your work! I’m missing the part where the “updateStatusByAccessCode()” method is add to the ‘user’ object

    1. Hi @julianlibor, you’re welcome! Thanks for the catch as well, I just added section “11.10 Add updateStatusByAccessCode() method” above.

  8. Julian Libor Avatar
    Julian Libor

    Hey, i’m missing also the “updateAccessCode()” for the “user”

    1. Thanks again @julianlibor, I just added section “12.6 Add updateAccessCode() method” above.

  9. SalmanAziz Avatar
    SalmanAziz

    From all the tutorials your is the best. thanks a lot .

    1. You’re welcome and thanks for your positive feedback @SalmanAziz! Please share our site to your friends. ๐Ÿ™‚

  10. just awesome man. loved it.

    1. You’re welcome @that_s_enam! I’m glad to know you loved our work!

  11. sergio wolf Avatar
    sergio wolf

    Hi Mike, I want to congratulate you for your work, I really enjoyed it, but I could not get any link any item “8.6 User object create () method” a $ this-> function showError ($ stmt); you can post to me, thanks.

    1. Hi @disqus_0kiwAkh3lj, thanks for the kind words! Would you provide a screenshot of the error message?

      1. sergio wolf Avatar
        sergio wolf

        Sorry, I was not clear, I have not found a function $ this-> function showError ($ stmt) “in source code 8.6 User object create () method”, I’m still a beginner :). I wanted to know if you have her description.

        1. Hi @disqus_0kiwAkh3lj, thanks for pointing this out, I added the showError() method’s code on section 8.7 above.

      2. Free Thinker Avatar
        Free Thinker

        I’m having same problem.
        Fatal error: Uncaught Error: Call to undefined method User::showError() in /Library/WebServer/Documents/php_login_system/objects/user.php:126 Stack trace: #0 /Library/WebServer/Documents/php_login_system/register.php(56): User->create() #1 {main} thrown in /Library/WebServer/Documents/php_login_system/objects/user.php on line 126

        user.php on line 126 – $this->showError($stmt);

        1. Hi @disqus_yWhVHNJCiR, thanks for your comment, I added the showError() method’s code on section 8.7 above. Let me know if that worked for you.

  12. Abid Siddique Avatar
    Abid Siddique

    Yes, no doubt this script is wonderful, thanks for sharing it.

    I am facing *ERROR: Access code not found.* issue

    Am i missing something ??

    1. Hi @disqus_v10UkPONq8, I’m unable to replicate the issue, it works on my end. Are you sure the access code is being saved on your database? Please check your PhpMyAdmin.

  13. You’re welcome @sergiowolffedychen!

  14. Hi @disqus_qVmqkTIGsc, make sure your ID field in the users table has a primary key and auto-increment property.

  15. Jalal Maqableh Avatar
    Jalal Maqableh

    Hi Mike, First, I would like to thank you for this great tutorial, I learned a lot since I’m from the old programming generation. The way you explain this work help people understand what is going on in each step. I did the whole coding including sections 12 & 13. There is only one thing I want to share with you about point (12.1 Add forgot password link), the following code kept giving me error till I added “echo” for each line of code.

    Forgot password?

    Thank you very much

    1. Hi @JalalMaq, you’re welcome and thanks for the kind words! I’m glad our tutorial has helped you. I updated section 12.1 with your suggestion, thank you for pointing this out! I’m sure this will help other students of our site. ๐Ÿ™‚

  16. Hi @disqus_qVmqkTIGsc, you can always use the green download button above to download our code. ๐Ÿ™‚

  17. Hi @disqus_qVmqkTIGsc, the email feature won’t work on localhost. You need a paid remote server to make it work. I suggest Bluehost.

  18. Hi @zaem_shakkir, make sure your CSS file is linked properly in the head section of your page.

  19. Hi @zaem_shakkir, would you show us a screenshot of the issue? Which error message did you encounter?

  20. Would you describe in detail what exactly is the issue on your page?

  21. Hi @zaem_shakkir, thanks for the kind words! Make sure the page it redirects to is working so it won’t be blank.

  22. Hi @miracle_michael, you can download the source code using the green download button above. ๐Ÿ™‚

  23. Nathan Francoeur Savoie Avatar
    Nathan Francoeur Savoie

    Really clear and easy to follow !
    I will just modified the following line in .htaccess file for : Options +Multiviews
    I have replaced the minus sign by the plus sign in order to make this work without the extension .php
    Thanks again ๐Ÿ™‚

    1. Hi @nathanfrancoeursavoie, thanks for the kind words and tips! I’m sure this is useful for people who encountered the same issue.

  24. HI @zaem_shakkir, I’m unable to replicate the issue, but if you can see the link, you can copy and paste it on a new tab on your browser.

    1. Zaem Shakkir Avatar
      Zaem Shakkir

      I follow all ur steps , but when it comes to verify email , i got the email from the server , and it goes to spam msg , when i click the link given to verify email , its dont work

      1. Hi @zaem_shakkir, which web hosting provider do you use? I suggest using a different email server when sending email. I recommend sendgrid.com or postmarkapp.com so that it won’t go to spam folder.

  25. Hi @Var1984, are you sure your database name is “php_login_system”?

    1. Vanver Brown Avatar
      Vanver Brown

      I double and tripled checked it and it is named correctly, What I am wondering is if it may be a port issue, I had to switch the port to 8080 for Apache due to conflicts on port 80?

      1. I’m not sure about your local server settings. How about your database username and password? If you have a remote server, I suggest testing it there as well.

        1. Vanver Brown Avatar
          Vanver Brown

          Thanks for the help Mike, I actually went back through the database and realize are had wrong settings on it. Once I rebuilt the database it started working, thanks..

          1. You’re welcome @Var1984! Please share our site to your friends, you might help them as well. ๐Ÿ™‚

  26. Hi @muhamadfahrizanovriansyah, it looks like your objects/user.php file does not exist or it is in the wrong folder. Please make sure it is in the correct directory.

  27. Hi @mac, please add the emailExists() method as instructed in section 7.8. Try to add it before the last closing curly brace.

  28. Hi @dennisandrian, would you tell us the error message you see on your console? Another solution is to remove the logout button from your drop down and put it in the navigation bar.

  29. Great! I’m glad it works for you now.

  30. Katrina Engelbrecht Avatar
    Katrina Engelbrecht

    Thank you very much for your awesome work, Mike!

  31. hi , read_users list is possible to show without number of pages ? single pages show all list

  32. Your code is great and wonderful but there is 1 problem in the code How can I fix this error?
    Warning: mail(): Failed to connect to mailserver at “localhost” port 25, verify your “SMTP” and “smtp_port” setting in php.ini or use ini_set() in C:\xampp1\htdocs\PHPOOPLogin\libs\php\utils.php on line 38

Leave a Reply

Your email address will not be published. Required fields are marked *