Posted on: December 15, 2017
PHP registration form with email confirmation
A time comes when a project requires more than just one time user that will be managing a blog or a website so, for this purpose alone, we will be looking at how to create a simple registration form using PHP and MYSQL. To protect against spam user creation, I'll include the captcha condition that anyone using the registration form will have to pass to create an account. After the account is created, an email activation link with a unique key is assembled and sent to the email address provided in the form. When the link is clicked on, the account gets activated and the unique key associated with the id gets deleted from the database.
The tutorial will be done in 3 parts to inspect each file that will be created for this purpose. First part will contain a custom function to handle errors that will be created from unpassed conidtions, the second part will contain the main functionality of the script and HTML form and the third part will be about the account activation. So to begin with the tutorial, you'll need to include the following tables to the database:
CREATE TABLE `sql_users` (
`id` int (11 ) NOT NULL PRIMARY KEY AUTO_INCREMENT
`first_name` varchar (100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL
`last_name` varchar (100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL
`address` varchar (100 ) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL
`username` varchar (100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL UNIQUE KEY
`password` char (64 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL
`email` varchar (250 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL UNIQUE KEY
`status` varchar (10 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
CREATE TABLE `acc_activation` (
`id` int (11 ) NOT NULL PRIMARY KEY AUTO_INCREMENT
`activation_key` char (64 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL
`uid` int (11 ) NOT NULL UNIQUE KEY
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
PART 1 - Handling conditions and errors (con_function.php)
The title basically says it all. One thing to note is that we'll also return $username and $email variable to the array that will be passed to a list(PART 2) in the main file. This is only to avoid repeating the escaping of the input for username and email. And the reason we need to do that here in this file is because we'll check whether or not the username and email address are already in use.
<?php
// Create a function and pass $mysqli variable to it from DB config file that is located in the main file.
function con($mysqli ) {
// Escape the input and save username to a variable. Run a query to compare the username against all usernames
// Count the rows, important for condition below. Basically same username cannot be used more than once
$username = mysqli_real_escape_string($mysqli , $_POST['uname' ]);
$uquery = mysqli_query($mysqli , "SELECT * FROM sql_users WHERE username = ' $username '" );
$newuser = mysqli_num_rows($uquery );
// Repeat the process for email. Same email can only be used once
$email = mysqli_real_escape_string($mysqli , $_POST['email' ]);
$mquery = mysqli_query($mysqli , "SELECT * FROM sql_users WHERE email = ' $email '" );
$newmail = mysqli_num_rows($mquery );
// When the register button is clicked on, execute below...
if (isset ($_POST['register' ])) {
// Conditions that have to be met. It basically check for empty First name input or the input longer than 50 chars
if (empty ($_POST['fname' ])) {$fnameerr = '<span style="color:red;">First Name field is empty!</span>' ;}
elseif (strlen($_POST['fname' ]) > 50 ) {$fnameerr = '<span style="color:red;">First name too long! Max 50 characters allowed.</span>' ;}
// Same as above, but for Last name
if (empty ($_POST['lname' ])) {$lnameerr = '<span style="color:red;">Last Name field is empty!</span>' ;}
elseif (strlen($_POST['lname' ]) > 50 ) {$lnameerr = '<span style="color:red;">Last name too long! Max 50 characters allowed.</span>' ;}
// Check that the address is no longer than 100 chars
if (strlen($_POST['address' ]) > 100 ) {$adderr = '<span style="color:red;">Address too long! Max 100 characters allowed.</span>' ;}
Same as First and Last name but below it also checks for unique username in DB.
if (empty ($_POST['uname' ])) {$usererr = '<span style="color:red;">Name field is empty!</span>' ;}
elseif (strlen($_POST['uname' ]) > 100 ) {$usererr = '<span style="color:red;">Username too long! Max 50 characters allowed.</span>' ;}
elseif ($newuser !== 0 ) {$usererr = '<span style="color:red;">Username already in use! Please use a different username</span>' ;}
// Check for empty email, to match regex pattern and whether the email is unique or not
if (empty ($_POST['email' ])) {$mailerr = '<span style="color:red;">Email field is empty!</span>' ;}
elseif (!preg_match("[_az09](.[_az09])[az09](.[az09])(.[az]{2,3})$i" , $_POST['email' ])) {$mailerr = '<span style="color:red;">The email address is not valid!</span>' ;}
elseif ($newmail !== 0 ) {$mailerr = '<span style="color:red;">Email address already in use! Please use a different address</span>' ;}
// Check for empty password input, check if shorter than 8 chars and whether the password and confirm password match
if (empty ($_POST['passw' ])) {$passerr = '<span style="color:red;">Password field is empty!</span>' ;}
elseif (strlen($_POST['passw' ]) < 8 ) {$passerr = '<span style="color:red;">Password too short! It needs to be at least 8 chars long.</span>' ;}
elseif ($_POST['passw' ] !== $_POST['cpassw' ]) {$cpasserr = '<span style="color:red;">Passwords do not match!</span>' ;}
// Check whether the Captcha code input matches the one generated in the image and stored in a session
if (hash('sha256' , $_POST['verify' ]) !== $_SESSION['code' ]) {$codeerr = '<span style="color:red;">The verification code does not match!</span>' ;}
}
// Return an array of variables needed for the script
// Note that we have also included $username and $password because they are already escaped
return array($fnameerr , $lnameerr , $adderr , $usererr , $mailerr , $passerr , $cpasserr , $codeerr , $username , $email );
}
PART 2 - Main script and registration form (registration.php)
The functionality created, which we'll do first, will contain the creation of user account, creation of unique key for the activation link and assembly of a mail that will send the link to the email address provided in the form upon account creation. After, we'll create the HTML registration form. The one thing we won't cover here is the databse config file. For more information on how to connect to MYSQL database, click here. Note, however, that you'll need a PHP procedural style config file. So let's get started.
<?php
// Start a session(because of Captcha) and include db_config file and the above condition function
session_start();
include_once ('db_config.php' );
include_once ('con_function.php' );
// Unpack all the variables from con() function
list ($fnameerr , $lnameerr , $adderr , $usererr , $mailerr , $passerr , $cpasserr , $codeerr , $username , $email ) = con($mysqli );
// Create a new $errors variable and append all, potentially, created errors to it
$errors = '' ;
$errors .= $fnameerr .$lnameerr .$adderr .$usererr .$mailerr .$passerr .$cpasserr .$codeerr ;
// When the register button is clicked on, now check for empty $errors variable before going any further
if (isset ($_POST['register' ])) {
if (empty ($errors )) {
// If there are no errors, escape the remaining input elements and save them to variables
// Note that username and email were already escaped, unpacked to a list and are ready to use
// $password variable is not escaped because it won't be used in SQL query
$fname = mysqli_real_escape_string($mysqli , $_POST['fname' ]);
$lname = mysqli_real_escape_string($mysqli , $_POST['lname' ]);
$password = $_POST['passw' ];
// Set the iteration rounds and then generate PBKDF2 32 byte Hex passhash output from the variables to be inserted as password in DB
$rounds = 50000 ;
$passhash = hash_pbkdf2('sha512' , $password , $username , $rounds , 64 );
// Since address column is by default NULL, assign it to $address variable if the input is empty, else escape the input
if (empty ($_POST['address' ])) {$address = NULL ;}
else {$address = mysqli_real_escape_string($mysqli , $_POST['address' ]);}
// Generate unique activation key from IP, user agent, time and random number
$randint = mt_rand(10000 , 99999 );
$key = hash('sha256' , ($_SERVER['REMOTE_ADDR' ].$_SERVER['HTTP_USER_AGENT' ].time().$randint ));
// Insert a user into the database. Status is by default pending whenever a user is created and all the variables used were previously escaped.
// All but $passhash which doesn't need to be
mysqli_query($mysqli , "INSERT INTO sql_users (first_name, last_name, address, username, password, email, status) VALUES (' $fname ', ' $lname ', ' $address ', ' $username ', ' $passhash ', ' $email ', 'pending')" );
// Get the id of the last inserted row and insert the activation key, id and timestamp to acc_activation table
// Id is for reference so that the activation file(PART 3) will know which account is being activated
$id = mysqli_insert_id($mysqli );
mysqli_query($mysqli , "INSERT INTO acc_activation (activation_key, uid, timestamp) VALUES (' $key ', ' $id ', CURRENT_TIMESTAMP)" );
// Assemble the email. Note that link will point to the activate.php file(PART 3) with included $key and $id variable that were created as a part of adding a user to DB
$to = $email ;
$link = "http://www.your-registration-form.com/activate.php?key= $key &id= $id " ;
$subject = "User Registration Activation Email" ;
$content = "Click this link to activate your account" ." \n" .$link ;
$from = "From Webdevelopmentblog \r\n" ;
// Send the email and if sent create a $success variable
if (mail($to , $subject , $content , $from )) {$success = '<span style="color:green;">You have registered and the activation mail is sent to your email. Click the activation link to activate your account.</span>' ;}
}
}
?>
Now we create the HTML form where we'll append separate error variables under the each input accordingly. So if the error is generated it will be displayed under the particular label. Additionally, all but password, when posted, will be saved to the inputs so the user won't have to re-type everything from scratch, if there is an error. Note that the echoing of these inputs will be encoded with htmlentities() function to prevent XSS.
<h4> * Required fields</h4>
<form action="" method="post" >
<label><strong> *</strong> First name:</label><br/>
<input type="text" name="fname" size="30" value=" <?php if ($_POST['fname' ]) {echo htmlentities($_POST['fname' ]);} ?> "> <br/>
<?php if (!empty ($fnameerr )) {echo $fnameerr ."<br/>" ;} ?>
<label><strong> *</strong> Last name:</label><br/>
<input type="text" name="lname" size="30" value=" <?php if ($_POST['lname' ]) {echo htmlentities($_POST['lname' ]);} ?> "> <br/>
<?php if (!empty ($lnameerr )) {echo $lnameerr ."<br/>" ;} ?>
<label><strong> *</strong> Address:</label><br/>
<input type="text" name="address" size="30" value=" <?php if ($_POST['address' ]) {echo htmlentities($_POST['address' ]);} ?> "> <br/>
<?php if (!empty ($adderr )) {echo $adderr ."<br/>" ;} ?> <br/>
<label><strong> *</strong> Username:</label><br/>
<input type="text" name="uname" size="30" value=" <?php if ($_POST['uname' ]) {echo htmlentities($_POST['uname' ]);} ?> "> <br/>
<?php if (!empty ($usererr )) {echo $usererr ."<br/>" ;} ?>
<label><strong> *</strong> Email address:</label><br/>
<input type="text" name="email" size="30" value=" <?php if ($_POST['email' ]) {echo htmlentities($_POST['email' ]);} ?> "> <br/>
<?php if (!empty ($mailerr )) {echo $mailerr ."<br/>" ;} ?>
<label><strong> *</strong> Password:</label><br/>
<input type="password" name="passw" size="30" /><br/>
<?php if (!empty ($passerr )) {echo $passerr ."<br/>" ;} ?>
<label><strong> *</strong> Confirm Password:</label><br/>
<input type="password" name="cpassw" size="30" /><br/>
<?php if (!empty ($cpasserr )) {echo $cpasserr ."<br/>" ;} ?>
<-- Assuming the catpcha.php file is in the root folder -->
<p><strong> *</strong> Please enter the verification code below:</p>
<img style="vertical-align:middle;" src="/captcha.php" > <input type="text" name="verify" maxlength="6" ><br/>
<?php if (!empty ($codeerr )) {echo $codeerr ."<br/>" ;} ?> <br/>
<input type="submit" name="register" value="Register" ><br/><br/>
</form>
<-- Displaying the success message if it is generated(see above) -->
<?php echo $success ; ?>
If you have trouble with implementing captcha, you can use referrence here, particulary in part 2 of the post.
PART 3 - account activation (activate.php)
The account has already been created and the activation link sent to the email address provided in the registration form. However, we still need to define the activation process. In the link, that was sent to the email, was the activation key and id of the user. The latter binds the user to the activation key. So both have to match to activate the account. After the account is activated, a record in acc_activation table in MYSQL database will erase itself.
<?php
// Include DB config file
include_once ('db_config.php' );
// Get the key from url, it doesn't have to be escape because we won't use it in the query
// The id, on the other hand, needs to be escaped
// Look inside the acc_activation table where uid matches $id from the url, and fetch an array
$key = $_GET['key' ];
$id = mysqli_real_escape_string($mysqli , $_GET['id' ]);
$query = mysqli_query($mysqli , "SELECT * FROM acc_activation WHERE uid = ' $id '" );
$fetch = mysqli_fetch_array($query );
// If key from url matches the one in the row found in the query, execute below...
if ($key === $fetch ['activation_key' ]) {
// Update status in the sql_users table where id matches id from url
// And delete the record from acc_activation table
mysqli_query($mysqli , "UPDATE sql_users SET status = 'active' WHERE id = ' $id '" );
mysqli_query($mysqli , "DELETE FROM acc_activation WHERE uid = ' $id '" );
echo "Your account has been activated." ;
}
else {echo "Something went wrong" ;}
That's it, the basic registration concept has been explained but you'll have to wait for the script a bit longer. I will assemble it after we discuss how to reset a password. It will include the code here and login form with the password reset option.