Coding a registration system with email verification using php and mysql

Updated: 16-Mar-2023 | Tags: PHP and MYSQL | Views: 1601

Introduction

Hi guys. In this article we are going to create a complete secure registration system. That means that we are gonna have a register page, an email verification page, where we are going to send a 6 digits code to the user to verify his email, a login page, a forgot password page, where the user will fill his email and we are going to send him a new password. And an account page where we redirect the user when he successfully log's in.

Try out the live example so you will have a better understanding on what we are going to code.

Live example

I have prepared a live demo so you can play around to see how it works. Use your real email so you can try out the email verification action, and the forgot password action.

Don't worry your email will not be shared or used, in fact it will be deleted after a few days or so. Every field has a secure validation, so treat the live demo as a real registration process.

Now that you have tried out the demo let me explain the logic behind the scenes.

The logic (algorithm)

  • Our application starts with the register page.
    When the user fill's out all the fields correctly and presses the register button the following actions will take place:
  • A 6 digits verification code is created and send to the email that the user has entered.
  • The values from the register form are inserted in the database along with two more values, an email-status default value which is "pending", and the 6 digits verification code.
  • The user is redirected to the verification page.
  • The user has to insert the verification code that he received.
  • If the user don't received the code he has the option to resend the verification code.
  • If the code is correct the email-status value in the database will be updated from "pending" to "verified", and he will be redirected to the login page so he can log-in to his account.
  • If the log-in process is successful the user will be redirected to his account.

Of course the above steps are very simplified, but you get an idea how the process go.
Now let's see what files we need to create the application.

Project's folder

We will need the following files.

  • A register.php file, to create the html for the register form.
  • We will have an auth.php file, to verify the email.
  • A login.php file, to create the login form.
  • A forgot-password.php file, to reset the user's password.
  • A config.php file to store the user's database log-in details.
  • A functions.php file, where we are going to write all our functions.
  • An account.php file, which is the user's account page.
  • And a styles.css file to make the application look pretty. But i will not go through and explain the css code, because it has nothing to do with the logic's application.

If you want to follow along, Click here to download the project's structure and the user table SQL file.
Else you can scroll to the bottom of the page and by me a coffee to get the source code.

Now that we have the file structure let's start codding, and first of all let's see the database table where we are going to record the user.

The users database table

The users table has an id column, an email, a username, a password, which is encoded, an email_status column which will take the value "pending" as its default value at first, and will updated to "verified" later on, and the verification_code which will be our reference point to update the email_status to "verified".


id email username password email_status verification_code
1 digitalfox.tutorials@gmail.com George $2y$10$B/mz9bmnO pending or verified 565657

The important thing here is that you must set the length of the password column to 255 (varchar 255), so you will be sure that the encoded password will be inserted whole and not truncated. But if you get the source code you will get also the users.sql file.

Also you have to set to "pending" the default value in the email_status column.

Now let's see the config.php file.

The config file

This is our config.php file and will be included in the functions.php file so we can have access to the database login details.

<php 
	session_start();
	define('SERVER', 'localhost');
	define('USERNAME', '');
	define('PASSWORD', '');
	define('DATABASE', '');
  • In line 2 we start a session. We will use a user session, $_SESSION['user'], when the user logs in.
  • Next we have to insert our database login details, so we can connect to the mysql server.

Now let me explain how the remaining tutorial is structured. We are going to create each page separately.
In example, i am going to start with the register's page html, and also i will write the php code. I will make each page to work, and then i will move to the next one.
So let's start with the styles.css file.

The CSS file

As i said i won't go through and explaining the css rules, there is nothing fancy going on here. If you follow along copy the css code and place it in your styles.css file. This way any html code that we write next will have its style rule applied.

*{
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}
body{
	font-family: sans-serif;
	min-height: 100%;
	color: #555;
}
form{
	max-width: 400px;
	margin: 50px auto;
	border:  thin solid #e4e4e4;
	padding: 20px;
	box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2);
}
form h2{ margin-bottom: 10px; }
form .info{
	margin-bottom: 20px;
	line-height: 24px;
}
form label{
	display: block;
	margin-bottom: 10px;
	padding-left: 5px;
}
form input, form textarea{
	display: block;
	width:  100%;
	padding: 10px;	
	margin-bottom: 10px;
	font-size: 16px;
	border:  thin solid #e4e4e4;
	margin-bottom: 30px;
}
form select{
	display: block;
	width:  100%;
	margin-bottom: 10px;
	padding: 10px;
	font-size: 16px;
	border:  thin solid #e4e4e4;
	background-color: white;
	color: #555;
}
form input:focus{ outline: none; }
form input::placeholder{ font-size: 16px; }
form button{
	background: #32749a;
	color: white;
	border: none;	
	padding: 15px;
	width:  100%;
	font-size: 17px;
	margin-top: 20px;
	cursor: pointer;
}
form button:hover{ filter: brightness(1.4); }
form button:active{ background-color: green; }
form .forgot-password, form .resend-code, form .have-account{
	margin-top: 30px;
}
form a{	color: #1f7498; }
form a:hover{ text-decoration: none; }
form .create-account{ margin-top: 15px; }
.error{
	margin-top: 30px;
	color: #af0c0c;
}
.success{
	margin-top: 30px;
	color: green;
	line-height: 26px;
}
.page{
	max-width: 900px;
	margin: 30px auto 0 auto;
	padding: 20px;
}
.top-bar{
	display: flex;
	justify-content: space-between;
	align-items: center;
	border-bottom: thin solid #d4d4d4;
	padding-bottom: 20px;
}
.top-bar a{
	background: #32749a;
	color: white;
	text-decoration: none;
	padding: 8px 35px;
	font-size: 17px;
}
.top-bar a:hover{ filter: brightness(1.4); }
.top-bar a:active{ background-color: green; }
.content{ margin-top: 100px; }
.content h3{ margin-bottom: 20px; }
.content p{
	line-height: 26px;	
	margin-bottom: 50px;
}		
	

The above css applies the same styling that you see in the live demo.

Okay, now that we have set up the css file, let's code our first task and register the user in the database.

The register page

The first thing that we will do in the register.php file is to require the functions.php at the top of the page. This way we will have access to the functions that we are going to write there.

<?php require "functions.php" ?>

Next we are going to check if there is a POST request that hold's a key named 'register'.
The POST request will be initiated from the register form that we are going to write further down.

<?php 
	if(isset($_POST['register'])){
		$response = register($_POST['email'], $_POST['username'], $_POST['password'], $_POST['confirm-password']);
	}
?>

Now if the if statement returns true, and there is a POST request we are going to run a function named register(), and we are going to pass-in as arguments, the form's fields values, which are the email, the username, the password, and the confirm-password, and we are going to store any error that the function returns in the $response variable.

It's obvious that the function doesn't exist yet. We are going to write the function in the functions.php file after we complete the register.php file that we are working on.

Next we are going to write a simple html page structure. In here we have a link in line 12 that points to our css file.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<link rel="stylesheet" href="styles.css">
	<title>Register page</title>
</head>
<body>
	
	<!-- We are going to place the register form here -->

</body>
</html>

Next inside the body tags we will write the register form.

	<form action="" method="POST">
		<h2>Register form</h2>
		<p class="info">
			Please fill out the form to create your account.
		</p>

		<label>Email</label>
		<input type="text" name="email" value="<?php echo @$_POST['email'] ?>">
		
		<label>Username</label>
		<input type="text" name="username" value="<?php echo @$_POST['username'] ?>">
	
		<label>Password</label>
		<input type="text" name="password" value="<?php echo @$_POST['password'] ?>">

		<label>Confirm Password</label>
		<input type="text" name="confirm-password" value="<?php echo @$_POST['confirm-password'] ?>">
	
		<button type="submit" name="register">Register</button>
		
		<p class="have-account">
			<a href="login.php">Already have an account?</a>
		</p>
		
		<p class="error"><?php echo @$response ?></p>		
	</form>

Explaining the register form

Now let's see what we have here.

  • In line 17 we have the action attribute empty (action=""), that means that we are sending the POST request to the same page. That's why we check for a POST request at the top of the page.
    And to send a POST request we have to set the method attribute to POST, (method="POST").

    <form action="" method="POST">
  • Next in line 24 we have the email's input field.

    <input type="text" name="email" value="<?php echo @$_POST['email'] ?>">
    • The type attribute is set to text, (type="text"). Normally we have to set the type attribute to email, (type="email"), so we have a quick browser validation, but i set it to text because i want to show you how we validate it with php. Which we always must do.
    • Next we have the name attribute set to email, (name="email"). The name attribute is holding the field's value, that's why we use it in our php code to get it ($_POST['email']);
    • Next we have the value attribute. Now in here i display with php the submitted value

      (echo @$_POST['email']), that means that when we submit the form and there is an error we will not loose what we have typed in. It's also called a sticky form.

      The @ character in front of the POST variable tells the server not to throw a warning if there is not a POST request.That happens if we first land on the page.

      The above explanation applies to all the input fields that we have in the register form.

  • The username input field

    <input type="text" name="username" value="<?php echo @$_POST['username'] ?>">
  • The password field.

    <input type="text" name="password" value="<?php echo @$_POST['password'] ?>">
  • And the confirm-password field.

    <input type="text" name="confirm-password" value="<?php echo @$_POST['confirm-password'] ?>">
  • Next we have a button. We set the type attribute to "submit". This will create the form's submit button.
    The name attribute is set to "register". This is the key that we check in the if statement at the top of the page if the form is submitted (if(isset($_POST['register']))).

    <button type="submit" name="register">Register</button>
  • Next we have a link that goes to the login.php page if the user has already an account and wants to log-in.

    <a href="login.php">Already have an account?</a>
  • And last we have a paragraph element with a class of "error". In here we will display any validation error that the register function returns.

    <p class="error"><?php echo @$response ?></p>

    And that is all the code that we need in the register.php page. Now let's go to the functions.php file and make the register form to work.

Coding the register function in the functions php file

The first thing that we will do in the functions.php file is to require the config.php file so we have access to the database login details.

<?php 
require "config.php";

Next we are going to write a function named dbConnect() to connect our php file with the mysql server and the database.

function dbConnect(){
	$mysqli = new mysqli(SERVER, USERNAME, PASSWORD, DATABASE);
	if($mysqli->connect_errno != 0){
		return FALSE;
	}else{
		return $mysqli;
	}
}
  • In line 4 we use the constants from the config.php file, as arguments in the mysqli object.
  • The function returns FALSE if there is an error, else, the function returns the mysqli object.

Next we are going to define the register() function.

function register($email, $username, $password, $confirm_password){
  • The register() function takes four parameters. The email, the username, the password, and the confirm-password values. Remember that this function runs at the top of the register.php page.

Next in line 13, and inside the register() function, we are going to call the dbConnect() function to connect to the database. We will store the returned mysqli object in the $mysqli variable.
We are gonna use the $mysqli variable to perform queries to our database users table.

	$mysqli = dbConnect();
	if($mysqli == false){
		return false;
	}
  • In line 14 we check if our database connection failed. If this is true, we return false in line 15 and stop the register function here.

Next we will create an associative array named $args, (as arguments), which will hold the function's arguments. This way we are going to make some validation tasks much easier, and we are gonna write less code. You will see.

	$args = [
		"email" => $email,
		"username" => $username,
		"password" => $password,
		"confirm-password" => $confirm_password
	];

Next we are going to use the array_map() function to go through every value that the $args array holds and trim any white-space from the values that they might have.

	$args = array_map(function($value){
		return trim($value);
	}, $args);
  • The array_map() function will return an array which holds the trimmed values. We are going to take this array and store it back to the $args variable.
  • This is more easier to do now using the $args array, instead of trimming every argument separately like this:
    	$email = trim($email);
    	$username = trim($username);
    	$password = trim($password);
    	$confirm_password = trim($confirm_password);
    		

Next we are going to loop again through the $args array but this time we are gonna use the foreach() function.

	foreach ($args as $value) {
		if(empty($value)){
			return "All fields are required";
		}
	}
  • We are going to check for empty values. If there is an empty field we are going to return a message that says, "All fields are required", and the function stops here.

Next we are going to loop again through the $args array, but this time we are going to search for any greater than (<) or less than (>) characters. To avoid any malicious javascript code inserted in our database.

	foreach ($args as $value) {
		if(preg_match("/([<|>])/", $value)){
			return "<> characters are not allowed";
		}
	}
  • If the preg_match() function finds any of those two characters, we return a message, and the register() function stops here.

Next we are going to loop one last time through the $args array, and this time we will sanitize the incoming values using the htmlspecialchars() function. In this way we add an extra security layer, although we are going to use prepared statements to insert the data in the database.

	$args = array_map(function($value){
		return htmlspecialchars($value);
	}, $args);

Next we are going to validate the email. Notice that we are validating the email from the $args array and not the $email argument.

	if(!filter_var($args["email"], FILTER_VALIDATE_EMAIL)){
		return "Email is not valid";
	}
  • We use the filter_var() function with the "FILTER_VALIDATE_EMAIL" flag as the second argument.
    If the email has not the right format we return an error saying that the "Email is not valid".

Next we are going to check the length of the username and password. If their length is greater than 20 characters we return an error.

	if(mb_strlen($args["username"]) > 20){
		return "The username must be under 20 characters";
	}

	if(mb_strlen($args["password"]) > 20){
		return "The password must be under 20 characters";
	}
  • To get the length of the username and password, we use the mb_strlen() function, so we are sure that we get the right length when multi-byte characters are inserted like Greek, Cyrillic, Chinese, or Japanese.
  • In these languages, a single character may be represented by multiple bytes, making it more difficult to accurately determine the length of a string without taking into account the multi-byte encoding. That's why it's important to use functions like mb_strlen() in PHP to correctly calculate the length of strings in these languages.
  • Here is a list of languages that are using multi-byte encoding. Chinese, Japanese, Korean, Thai, Vietnamese, Arabic, Hebrew, Russian (for certain characters), Greek (for certain characters), Indic languages such as Hindi, Bengali, Punjabi, etc.

Next we are going to query the users table to get all the users from the database. We are doing this because we want to check if the email and username are not already used by another user.

	$result_set = $mysqli->query("SELECT * FROM users");
	$data = array();
	while($row = $result_set->fetch_assoc()){
		array_push($data, $row);
	}
  • So in line 57 we have our query, where we selecting everything from the users table.
  • In line 58 we initialize an empty array named $data.
  • In line 59 we use a while loop to fetch every user as an associative array.
  • And in line 69 we use the array_push() function to insert every user in the $data array.
    Now the $data array is holding all the users that we have in the users table.

Next we loop trhough the $data array and check if the email is already used by another user.
If this is the case we return a message that the "Email already exists" and we stop the function here.

	foreach ($data as $value) {
		if($args["email"] == $value["email"]){
			return "Email already exists";
		}
	}

Next we are going to use again the same logic, but this time we check if the username exists.
Again if this is the case we return a message that the "Username already exists", and the function stops here.

	foreach ($data as $value) {
		if($args["username"] == $value["username"]){
			return "Username already exists";
		}
	}

Next we are going to create and send the verification code to the user.

	$verification_code = createVerificationCode($data);
	if(!sendVerificationCode($args["email"], $verification_code)){
		return "Error sending verification code. Please try again";
	}
  • In line 75 we create the verification code with a function called createVerificationCode(), which we are going to write right after. The function takes as an argument the $data array so we can create a unique verification code. You will see when we write the function.
  • In line 76 we have another function that we are going to write and that is the sendVerificationCode() function.

    This function will send the verification code, and takes two arguments. The first argument is the user's email $args["email"], and the second argument is the $verification_code.

    The function returns TRUE on success or FALSE on failure.
    If the function returns FALSE we return a message that says "Error sending verification code. Please try again".

Next we are going to hash the password before we insert it in the database.

	$hashed_password = password_hash($args["password"], PASSWORD_DEFAULT);

Next and last, we are using a prepared statement to insert the user in the database.
If you like to learn how we write prepared statements you can check out this article
How to write mysql prepared statements

	$stmt = $mysqli->prepare("INSERT INTO users(email, username, password, verification_code) VALUES(?,?,?,?)");
	$stmt->bind_param("sssi", $args["email"], $args["username"], $hashed_password, $verification_code);
	$stmt->execute();
	if($stmt->affected_rows != 1){
		return "An error occurred. Please try again";
	}else{
		$_SESSION['email'] = $args["email"];
		$_SESSION['verification-code'] = $verification_code;
		header("Location: auth.php");
		exit();
	}
} // Closing the register function.
  • In line 79 we prepare the statement. You see that instead of the actual values we use question-marks (?).
  • In line 80 we bind the question-marks with the values.
  • And in line 81 we execute the statement, and insert the user in the database.
  • In line 82 we check if the query was successful. If not we return a message the says "An error occurred. Please try again".
  • In line 84 we have the else clause which means that our query was successful, and we set an email session, and a verification_code session. We are going to use these sessions if the user wants us to resend the verification code. You will see this in the auth.php page.
  • And in line 87 we redirect the user to the auth.php page so he can verify his email by inserting the verification code. I hope you tried out the live demo so this makes more sense.
    And that's it that is all the code that we need to register the user in the database. It was a quite lengthy function.
    Now let's write the createVerificationCode() and the sendVerificationCode() functions to complete the register task.

Writing the createVerificationCode function

We are still in the functions.php file and under the register() function we are going to write the createVerificationCode() function.

function createVerificationCode($database_data){
	$str = "0123456789";
	$random = str_shuffle($str);
	$verification_code = substr($random, 0, 6);
	
	$codes = array_map(fn($value) => $value["verification_code"], $database_data);
	if(in_array($verification_code, $codes)){
		return createVerificationCode($database_data);
	}else{
		return $verification_code;
	}
}
  • In line 93 we have a string which contains the numbers from 0 to 9.
  • In line 94 we shuffle the string to get a random sequence.
  • And in line 95 we fetch the first six digits from the shuffled string. This way we have a 6 digit code.
  • Next we are going to check if the verification code is available in the database. I know that this is a random 6 digits number, but if there is a chance that the code already exists in the database i like to avoid any conflict.
    So in line 97 i create a variable named $codes the will hold all the verification codes from the database.
  • In line 98 we will use the in_array() function, and we are going to check if the $verification_code that we have just created exists in the $codes array.
  • If this is true, and the $verification_code exists in the $codes array, we run the same function again to create another one on line 99.
  • Else we return the $verification_code on line 101.
    And we are done with the createVerificationCode() function. Now let's write the sendVerificationCode() function.

Writing the sendVerificationCode function

Basically what we are doing here is to send the verification-code with an email using the mail() function.

function sendVerificationCode($email, $verification_code){
	$subject = "verification code";
	$body = "Verification code". "\r\n";
	$body .= $verification_code;

	$headers = "MIME-Version: 1.0" . "\r\n";
	$headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
	$headers .= "From: you@localhost.com \r\n"; 
	return mail($email, $subject, $body, $headers);
}

  • It is important that you use in the From: header the email account that you have defined in your php.ini file, else the email will end up in the spam folder, or will not send at all.
  • The function returns TRUE if the email is sent, or FALSE if not.
  • And by completing the sendVerificationCode() function we have completed the register task. We can now register the user and send the verification code.
    Our next page to create is the auth.php page where the user will insert the verification code to verify his email.

The email verification page

Now let's create the auth.php page. We are going to have the following php code at the top of the page.

<?php require "functions.php" ?>
<?php 
	if(!isset($_SESSION['verification-code'])){
		header("Location: login.php");
		exit(); 	
	}
	if(isset($_POST['verify'])){
		$response = verifyEmail($_POST['verification-code']);
	}
	// This if statement runs if the user clicks on the resent verification code link.
	if(isset($_GET['resend-verification-code'])){
		if(sendVerificationCode($_SESSION['email'], $_SESSION['verification-code'])){
			$success = "Please go to your email account and copy the 6 digits code";
		}else{
			$response = "Something went wrong, please try again";
		}
	}
?>

Let's explain what is happening here.

  • As we did in the register page we are going to require the functions.php file at the top of the page.
    <?php require "functions.php" ?>
  • In line 3 we check if there is a verification session set. We are doing this because we don't want the users to have access to this page, after they complete their registration process. If there is not a verification session set, we redirect the user to the login.php page.
    	if(!isset($_SESSION['verification-code'])){
    		header("Location: login.php");
    		exit(); 	
    	}
    			
  • In line 7 we check if there is a POST request that holds a key named "verify". This will happen if we press the verification's form submit button that we will write further down.

    	if(isset($_POST['verify'])){
    		$response = verifyEmail($_POST['verification-code']);
    	}
    		
    • If there is a POST request we are going to call the verifyEmail() function in line 4, and we will store any message that the function returns in the $response variable. The function takes as an argument the verification code that the user types in the form.

      The verifyEmail() function doesn't exist yet. We are going to write the function in the functions.php file when we complete the auth.php page that we are on.

  • The next if block in line 11 will run if the user clicks on the resent verification code link that we will have in the email verification form. This time we check for a GET request with a key named "resend-verification-code".

    	if(isset($_GET['resend-verification-code'])){
    		if(sendVerificationCode($_SESSION['email'], $_SESSION['verification-code'])){
    			$success = "Please go to your email account and copy the 6 digits code";
    		}else{
    			$response = "Something went wrong, please try again";
    		}
    	}
    		
    • If this is the case in line 12 we execute the sendVerificationCode() function which we wrote earlier, and pass in the email session, and the verification-code session. Remember we have set those two sessions in the register() function, after we inserted the user in the database. We did that for this exact reason, so we can resend the code.
    • If the sendVerificationCode() function returns TRUE, we creating a variable named $success and assign a message that says "Please go to your email account and copy the 6 digits code".
    • If the sendVerificationCode() function returns FALSE, we are using the $response variable and we tell the user that something went wrong in line 15.

Next below the php code we are going to create a basic html structure.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<link rel="stylesheet" href="styles.css">
	<title>Email verification</title>
</head>
<body>
	
	<!-- Verification form goes here  -->

</body>
</html>

Next inside the body tags we are going to create the email verification form.

	<form action="" method="post">
		<h2>Email verification</h2>
		<p class="info">
			Please go to your email account and type in the field below the
			<strong>6 digits verification code</strong> that you received.	
		</p>
		<p class="info">
			After we verify your email you will be redirected to the login page
			to log in to your account.
		</p>

		<label>Verification code</label>
		<input type="text" name="verification-code">
		
		<button type="submit" name="verify">Verify</button>

		<p class="resend-code">
			<a href="?resend-verification-code">Resend verification code</a>
		</p>
		
		<p class="error"><?php echo @$response ?></p>
		<p class="success"><?php echo @$success ?></p>		
	</form>	
  • In line 40 we have an input field. The name attribute is set to "verification-code". This is the input field that the user enters the received verification-code.
  • In line 42 we have the form's submit button. The name attribute is set to "verify". This is the attribute that we use at the top of the page to check if the form is submitted. Remember that if the user clicks on the submit button, the verifyEmail() function will run.
  • In line 45 we have the resend-verification-code link. If for any reason the user didn't received his verification code after the register process, he has the option to click on that link to receive the code again.
  • In line 48 we display any error that may occur.
  • And in line 49 we display the success message if the user clicks on the resend link.
  • And that is all the code that we need in the auth.php file. Now let's go back to the functions.php file to write the verifyEmail() function.

Writing the verifyEmail function

Now we are back to the functions.php file, and under the previous functions that we wrote we are going to write the verifyEmail() function.

function verifyEmail($verification_code){
	$mysqli = dbConnect();
	if($mysqli == false){
		return false;
	}

	if(trim($verification_code) == ""){
		return "Please enter the 6 digits code";
	}

	if(!preg_match("/^\d{6}$/", $verification_code)){
		return "The string expects a 6 digits number";
	}

	$res = $mysqli->query("SELECT verification_code FROM users WHERE verification_code = '$verification_code'");
	if($res->num_rows != 1){
		return "Wrong verification code";
	}else{
		$update = $mysqli->query("UPDATE users SET email_status = 'verified' WHERE verification_code = '$verification_code'");
		if($mysqli->affected_rows != 1){
			return "something went wrong. Please try again";
		}else{
			header("Location: login.php");
			exit();
		}
	}
}

Let's explain what is going on in the verifyEmail() function.

  • The first thing that we do inside the function is to connect to the database, and check if the connection is successful.

    	$mysqli = dbConnect();
    	if($mysqli == false){
    		return false;
    	}
    
  • Next we check if the user has entered the verification code. If the field is empty we return a message that says "Please enter the 6 digits code".

    	if(trim($verification_code) == ""){
    		return "Please enter the 6 digits code";
    	}
    
  • Next we use a regular expression to check if the user has entered exactly 6 digits. If not we return a message saying "The field expects a 6 digits number".

    	if(!preg_match("/^\d{6}$/", $verification_code)){
    		return "The field expects a 6 digits number";
    	}
    
  • Next we check if the given verification-code exists in the database. If not we return the message "Wrong verification code".

    	$res = $mysqli->query("SELECT verification_code FROM users WHERE verification_code = '$verification_code'");
    	if($res->num_rows != 1){
    	   return "Wrong verification code";
    	}
    
  • Else if the verification-code exists in the database, we update the corresponding email_status to "verified".

    	else{
    	  $update = $mysqli->query("UPDATE users SET email_status = 'verified' WHERE verification_code = '$verification_code'");
    	  if($mysqli->affected_rows != 1){
    	     return "something went wrong. Please try again";
    	  }else{
    	     header("Location: login.php");
    	     exit();
    	  }
    	}
    
    • If the update query fails we return the message "something went wrong. Please try again" in line 136.
    • Else we redirect the user to the login.php page, in line 139. so he can log-in to his account.

      And we are done with the email verification task. Next page that we are going to create is the login.php page.

The login page

So we are now in the login.php page.
Again, as we did in the other pages we will require the functions.php file, so we have access to the functions that we wrote, and continue to write.

<?php require "functions.php" ?>
<?php 
	if(isset($_POST['login'])){
		$response = login($_POST['username'], $_POST['password']);
	}
?>
  • Next in line 3 we check for a POST request that has a key of "login". If the form, that we are going to right further down is submitted, we call the login() function and pass-in as arguments the submitted "username", and "password".
    And we are going to store any message that the function returns in the $response variable.
    We are using the exact same logic in every page that we have created.

Next we are going to create a basic HTML structure for our login page.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<link rel="stylesheet" href="styles.css">
	<title>Login page</title>
</head>
<body>
	<!-- Login form goes here -->
</body>
</html>	

Next inside the body tags we have the login form

	<form action="" method="post" autocomplete="off">
		<h2>Login form</h2>
		<p class="info">
			Please enter your username and password to log-in.
		</p>

		<label>Username</label>
		<input type="text" name="username" value="<?php echo @$_POST['username'] ?>">
	
		<label>Password</label>
		<input type="text" name="password" value="<?php echo @$_POST['password'] ?>">
	
		<button type="submit" name="login">Login</button>
		
		<p class="forgot-password">
			<a href="forgot-password.php">Forgot your password?</a>
		</p>
		<p class="create-account">
			<a href="register.php">Create an account</a>
		</p>
		<p class="error"><?php echo @$response ?></p>		
	</form>

Let's see what we have here.

  • In line 24, we have the username field, with the name attribute set to "username", and as we did in the register form we set in the value attribute the submitted username.
  • In line 26 we have the password field.
  • In line 28 we have the submit button with the name attribute set to "login".
  • In line 31 we have a link to the forgot-password.php page. If the user forgets his password he can click on the link and we will send him a new one. We are gonna create the forgot-password page right after we are done with the login page that we are on.
  • In line 34 we have a link to the register.php page.
  • And last in line 36 we display any message the the login() function returns.
  • And this is all the code we need in the login.php page. Now let's go again to the functions.php file to write the login() function.

Writing the login function

Now let's write peace by peace the login() function.

function login($username, $password){
	$mysqli = dbConnect();
	if(!$mysqli){
		return false;
	}
  • In line 143 we define the login() function which takes the username and password as parameters.
  • In line 144 we connect to the database, and in line 145 we check if the connection is established. If not we return false, and stop the function here.

Next we trim any white-space from the username and password.

	$username = trim($username);
	$password = trim($password);	

Next we check if both fields have values. If not we return a message that says "Both fields are required".

	if($username == "" || $password == ""){
		return "Both fields are required";
	}

Next we write a prepared statement to fetch the user's data, based on the username.

	$sql = "SELECT * FROM users WHERE username = ?";
	$stmt = $mysqli->prepare($sql);
	$stmt->bind_param("s", $username);
	$stmt->execute();
	$result = $stmt->get_result();
	$data = $result->fetch_assoc();
	if($data == NULL){
		return "Wrong username or password";
	}
	
  • In line 161 we store the fetched user in the $data variable.
  • If the username don't exist in the database we return the message "Wrong username or password" in line 163.
  • In a login form validation we don't display errors for a specific field. We always say "Wrong username or password".
    Let's say that someone tries to hack an account. After a few attempts suddenly we display the message "wrong username". That is bad because now the hacker knows the password, and his next attempts are to figure out the username. So by displaying "Wrong username or password" we don't give anything away.

Next we are going to check the user's "email_status", and if it is "pending", that means that the user skipped the email verification process or something went wrong, maybe he closed the browser and the sessions were destroyed.
So we are going to set back the sessions and send him the verification-code that we stored in the database.

	if($data["email_status"] == "pending"){
		$_SESSION['email'] = $data["email"];
		$_SESSION['verification-code'] = $data["verification_code"];

		sendVerificationCode($data["email"], $data["verification_code"]);
		header("location: auth.php");
		exit();
	}
  • In line 167 we set the email session, and in line 168 we set the verification-code session.
  • In line 170 we run the sendVerificationCode() function.
  • And in line 171 we redirect the user to the auth.php page to verify his email.
  • The above code-block will run only if the "email_status" is "pending".

Next we are going to continue with the normal flow, and that means that the user has verified his email.
In this case we are going to check if he entered the correct password, and if so, we redirect him to his account.

	if(password_verify($password, $data["password"]) == FALSE){
		return "Wrong username or password";
	}else{
		$_SESSION["user"] = $username;
		header("location: account.php");
		exit();
	}
} // closing the login function.
  • In line 175 we use the password_verify() function to verify his password. Remember that the password in the database is encoded. We encoded the password with the password_hash() function, so now we have to use the password_verify() function to verify it. Those two functions are working together.
  • The password_verify() function takes two arguments. The first argument is the password that the user enters, and the second argument is the hashed password from the database. The function returns true if both passwords match, or false if not.
  • So in line 176 we return the message "Wrong username or password" if the function returns false.
  • Else in line 178 we set a user session that holds the username,
    and we sent the user to the account.php page in line 179.
  • And we are done with the login process.
    The next page that we are going to create is the forgot-password.php page. We have a link in the login form that sends the user to the forgot-password.php page if he forgets his password. So lets create the forgot-password page.

The forgot-password page

We are in the forgot-password.php page and again we have a block of php code at the top.

<?php require "functions.php" ?>
<?php 
	if(isset($_POST['send-email'])){
		$response = passwordReset($_POST['email']);
	}
?>
  • As we know by now we have to require the functions.php file.
  • And in line 3 we check for a POST request that holds a key named "send-email". If this is the case..
  • .. we run the passwordReset() function in line 4, and store the returned message in the $response variable.
    The function takes as an argument the email that the user types-in, in the form that we will create further down.
  • Of course the function doesn't exist yet. We are going to write the function after we complete the page that we are on.

Next and under the php code we are going to create a basic html page structure.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	 <link rel="stylesheet" href="styles.css">
	<title>Password reset</title>
</head>
<body>
	<!-- Forgot password form goes here -->
</body>
</html>		

Next inside the body tags we have the email form

	<form action="" method="post">
		<h2>Password reset</h2>
		<p class="info">
			Please enter your email so we can send you a new password.
		</p>

		<label>Email</label>
		<input type="text" name="email" value="<?php echo @$_POST['email'] ?>">
		
		<button type="submit" name="send-email">Send</button>

		<?php 
			if(@$response == "success"){
				?>
					<p class="success">Please go to your email account and copy your new password.</p>
				<?php
			}else{
				?>
					<p class="error"><?php echo @$response; ?></p>
				<?php
			}
		?>		

		<p class="forgot-password">
			<a href="login.php">Back to login page</a>
		</p>
	</form>

Let's explain the email form.

  • In line 23 we have the email input field. The name attribute is set to "email" and again we set in the value attribute the submitted email.
  • In line 25 we have the submit button. The name attribute is set to "send-email". We use the name attribute at the top of the page to see if the form is submitted.
  • Next in line 27 we have a php block in which we check the $response variable. If the $response variable is equal to "success" we tell the user to go to his email client and get the new password.
  • Else in line 34 we display any error that the passwordReset() function may return.
  • And in line 40 we have a link that sends the user to the login form so he can enter his new password.
  • And that is all the code we need in the forgot-password page. Now we will go back to the functions.php file to write the passwordReset() function.

Writing the passwordReset function

We are back in the functions.php file to write the passwordReset() function.

function passwordReset($email){
	$mysqli = dbConnect();
	if(!$mysqli){
		return false;
	}
  • Again we connect to the database and check if the connection was successful.

Next we are going to trim any white-space from the submitted email.

	$email = trim($email);

Next we validate the submitted email. If it has not the right format we return a message saying that the "Email is not valid".

	if(!filter_var($email, FILTER_VALIDATE_EMAIL)){
		return "Email is not valid";
	}

Next we check if the email exists in the database.

	$stmt = $mysqli->prepare("SELECT email FROM users WHERE email = ?");
	$stmt->bind_param("s", $email);
	$stmt->execute();
	$result = $stmt->get_result();
	$data = $result->fetch_assoc();
	if($data == NULL){
		return "Email doesn't exist in the database";
	}
  • If the email doesn't exist in the database we return the message "Email doesn't exist in the database", in line 202.

Next, and if the email exists in the database, we will create a random password which we will send to the user.

	$str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcefghijklmnopqrstuvwxyz";
	$password_length = 7;
	$shuffled_str = str_shuffle($str);
	$new_pass = substr($shuffled_str, 0, $password_length);
  • In line 205 we have a string that contains the numbers 0-9 and the English alphabet in lower and upper case.
  • In line 206 we set the password's length. We will take 7 random characters from the above string.
  • In line 207 we shuffle the string to reorder in a random way all the characters.
  • And in line 208 we take from the shuffled string the first 7 characters, and store them in the $new_pass variable. This way we have a random 7 characters long password.

Next we are going to set the email's parameters, and send the email using the mail() function in line 216.

	$subject = "Password recovery";
	$body = "You can log in with your new password". "\r\n";
	$body .= $new_pass;
	$headers = "MIME-Version: 1.0" . "\r\n";
	$headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
	$headers .= "From: Admin \r\n";
	$sent = mail($email, $subject, $body, $headers);
  • In line 216 we store the returned value that the mail() function returns in the $sent variable.
  • The mail() function returns true on success or false on failure.
  • If you like to learn how to send emails with the mail() function read this article:

    How to generate a random secure password with php

Next we check the $sent variable. If it is false we return the message, "Email not sent. Please try again".

	if($sent == FALSE){
		return "Email not sent. Please try again";
	}

Else if the mail() function returns true...

	else{
		$hashed_password = password_hash($new_pass, PASSWORD_DEFAULT);

		$stmt = $mysqli->prepare("UPDATE users SET password = ? WHERE email = ?");
		$stmt->bind_param("ss", $hashed_password, $email);
		$stmt->execute();
		if($stmt->affected_rows != 1){
			return "There was a connection error, please try again.";
		}else{
			return "success";
		}			
	}
} // closing the passwordReset function.
  • In line 222 we hash the password that we sent.
  • And next we update the previous password with the new hashed one in the database.
  • If there was a database error and the update function didn't work, we return in line 228 the message "There was a connection error, please try again".
  • Else we return the string "success".
  • And we completed the forgot-password.php page. Now let's go and create the last page of this tutorial and that is the user's account page.

The user's account page

The account.php page is the last page that we are going to create.

<?php require "functions.php" ?>
<?php 
	if(!isset($_SESSION['user'])){
		header("Location: login.php");
		exit();
	}
	if(isset($_GET['logout'])){
		logout();
	}
?>
  • Again we will have a php block at the top of the file.
  • In line 1 we require the functions.php file.
  • In line 3 we check if there is a user session set. If not we redirect the user to the login.php page to log-in. This way only logged in users can access this page.
  • In line 7 we check if there is a GET "logout" request. If this is true we run the logout() function to log-out the user.

Next under the php block we have the html code.

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	 <link rel="stylesheet" href="styles.css">
	<title>User account</title>
</head>
<body>

	<div class="page">
		<div class="top-bar">
			<h2>Welcome <?php echo @$_SESSION['user'] ?></h2>
			<a href="?logout">Logout</a>
		</div>
	</div>	

</body>
</html>	
  • In line 23 we have a welcome message and display the user's username which we have stored in a user session in the log-in process.
  • And in line 24 we have a link that sends to the same page a GET request containing the "logout" query string. So if the user clicks on the link the logout() function that we have at the top of the page.
  • Now let's go a last time to the functions.php file to write the logout() function.

Writing the logout function

This is a very simple task, we destroy the session inline 236, and we redirect the user back to the login page, in line 237.

function logout(){
	session_destroy();
	header("location: login.php");
	exit();
}

Summary

And that's it guys we completed the whole tutorial. We have created a complete and secure login and register system with an email verification, and a forgot password action.

Now this was a quite lengthy article, please let me know if everything is understandable.

I hope you find it helpful and you like it.

Source code

If you fell like avoiding all the copying and pasting, you can buy me a coffee and get the source code in a zip file.
Get the source code

The code is double and triple checked, and it works fine.

But be sure before you download the source code, that you have a local server installed, and you have configured your SMTP settings so you can send emails from your local server.

To check if you can send emails from your local server, create a file named test.php and copy and paste the code below. Then run the test.php file in the browser. Note that the email may go to the spam folder.

<?php
	mail("your real email", "subject: test mail", "message: This is a test email");

If you download the source code, please go to the config.php file first to set your settings

Buy me a coffee

If you like to say thanks, you can buy me a coffee.

Buy me a coffee with paypal

Comment section

You can leave a comment, it will help me a lot.

Or you can just say hi. 😉

Tutorial Categories