Simple User Registration With Email Verification In PHP MYSQL

Welcome to a tutorial on how to create a user registration form with email verification, using PHP and MySQL. So you have decided to open up your website for user registration?

A simplified user registration system consists of the following basic components:

  1. First, a database to store registered users.
  2. Next, a PHP library to deal with the registration process.
  3. Finally, an HTML registration form and the confirmation page itself.

Let us walk through the exact details on how to do all of these in this guide – Read on to find out!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

Download & Notes User Database User Library
Registration Pages Useful Bits & Links The End

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

EXAMPLE CODE DOWNLOAD

Click here to download the source code in a zip file – I have released it under the MIT License, so feel free to build on top of it if you want to.

 

QUICK NOTES

  • Create a new database and import 1-users.sql.
  • Edit 2-user-core.php.
    • E5 – Change the URL and email message to your own.
    • F4 – Add your own “welcome” email if you want.
    • G – Change the database settings to your own.
  • That’s all. Access 3a-register.php in the web browser.

If you spot a bug, please feel free to comment below. I try to answer questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

 

USER DATABASE

All right, let us now get started with a simple user table to safely store the user data.

 

USERS TABLE

1-users.sql
CREATE TABLE `users` (
  `user_id` int(11) NOT NULL,
  `user_name` varchar(255) NOT NULL,
  `user_email` varchar(255) NOT NULL,
  `user_password` varchar(128) NOT NULL,
  `user_status` varchar(1) NOT NULL DEFAULT 'P',
  `user_data` text
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `users`
  ADD PRIMARY KEY (`user_id`),
  ADD UNIQUE KEY `user_email` (`user_email`),
  ADD KEY `user_name` (`user_name`),
  ADD KEY `user_status` (`user_status`);
 
ALTER TABLE `users` CHANGE `user_id` `user_id` INT(11) NOT NULL AUTO_INCREMENT;

 

USER TABLE STRUCTURE

Field Description
user_id Primary key, the user ID. Auto-increment.
user_name The user’s full name.
user_email The user’s email address. Unique field to prevent multiple registrations.
user_password The password, which will be encrypted… at least obfuscated.
user_status The current user account status.

  • A – Active
  • S – Suspended
  • P – Pending confirmation
user_data An open text field for any additional data that you might want to add in the future. E.g. A JSON string of address, telephone, personal settings, etc… The reason why I kept this as an open text field is that it can be encrypted for additional security.

 

 

PHP USER LIBRARY

Next, we create the PHP user library to do all the heavy lifting.

 

PHP USER CORE

2-user-core.php
<?php
class Users {
  // (A) CONSTRUCTOR - CONNECT DATABASE
  private $pdo = null;
  private $stmt = null;
  public $error = null;
  function __construct () {
    try {
      $this->pdo = new PDO(
        "mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
        DB_USER, DB_PASSWORD, [
          PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
          PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
      );
    } catch (Exception $ex) { exit($ex->getMessage()); }
  }

  // (B) DESTRUCTOR - CLOSE CONNECTION
  function __destruct () {
    if ($this->stmt !== null) { $this->stmt = null; }
    if ($this->pdo !== null) { $this->pdo = null; }
  }

  // (C) GET USER BY EMAIL
  function getByEmail ($email) {
    $this->stmt = $this->pdo->prepare("SELECT * FROM `users` WHERE `user_email`=?");
    $this->stmt->execute([$email]);
    return $this->stmt->fetch();
  }

  // (D) GET USER BY ID
  function getByID ($id) {
    $this->stmt = $this->pdo->prepare("SELECT * FROM `users` WHERE `user_id`=?");
    $this->stmt->execute(array($id));
    return $this->stmt->fetch();
  }

  // (E) REGISTER NEW USER
  // RETURN CODE
  // 0 - ERROR, 1 - OK, 2 - REGISTERED
  // 3 - REGISTERED NOT ACTIVATED, 4 - REGISTERED BANNED
  function register ($name, $email, $pass, $cpass) {
    // (E1) CHECK IF USER REGISTERED
    $check = $this->getByEmail($email);
    if (is_array($check)) { switch ($check['user_status']) {
      default:
        $this->error = "$email is already registered.";
        return 0; break;
      case "A":
        $this->error = "$email is already registered.";
        return 2; break;
      case "P":
        $this->error = "$email has a pending activation.";
        return 3; break;
      case "S":
        $this->error = "$email has been suspended.";
        return 4; break;
    }}

    // (E2) CHECK PASSWORD
    if ($pass != $cpass) {
      $this->error = "Passwords do not match.";
      return 0;
    }
    
    // (E3) GENERATE NOT-SO-RANDOM TOKEN HASH FOR VERIFICATION
    $token = md5(date("YmdHis") . $email);
    
    // (E4) INSERT INTO DATABASE
    try {
      $this->stmt = $this->pdo->prepare(
        "INSERT INTO `users` (`user_name`, `user_email`, `user_password`, `user_data`) VALUES (?, ?, ?, ?)"
      );
      $this->stmt->execute([
        $name, $email, password_hash($pass, PASSWORD_DEFAULT), 
        json_encode(["confirm"=>$token])
      ]);
      $this->lastID = $this->pdo->lastInsertId();
    } catch (Exception $ex) {
      $this->error = $ex;
      return 0;
    }
    
    // (E5) SEND CONFIRMATION EMAIL
    // ! CHANGE TO YOUR OWN MESSAGE + URL !
    $url = "http://localhost/3b-confirm.php";
    $msg = sprintf(
      "Visit this <a href="%s?i=%u&h=%s">link</a> to complete your registration.",
      $url, $this->lastID, $token
    );
    if (@mail(
      $email, "Confirm your email", $msg,
      implode("\r\n", ["MIME-Version: 1.0", "Content-type: text/html; charset=utf-8"])
    )) { return 1; } else {
      $this->error = "Error sending out email";
      return 0;
    }
  }

  // (F) VERIFY REGISTRATION
  function verify ($id, $hash) {
    // (F1) GET + CHECK THE USER
    $user = $this->getByID($id);
    if ($user === false) {
      $this->error = "User not found.";
      return false;
    }
    if ($user['user_status']=="A") {
      $this->error = "Account already activated.";
      return false;
    }
    if ($user['user_status']=="S") {
      $this->error = "Account is suspended.";
      return false;
    }

    // (F2) HASH TOKEN CHECK
    $confirm = json_decode($user['user_data'], 1)['confirm'];
    if ($confirm != $hash) { 
      $this->error = "Invalid token.";
      return false; 
    }
    
    // (F3) ACTIVATE ACCOUNT IF OK
    try {
      $this->stmt = $this->pdo->prepare("UPDATE `users` SET `user_status`='A' WHERE `user_id`=?");
      $this->stmt->execute([$id]);
      $this->lastID = $this->pdo->lastInsertId();
    } catch (Exception $ex) {
      $this->error = $ex;
      return false;
    }

    // (F4) SEND WELCOME MESSAGE IF YOU WANT
    // mail ($user['user_email'], "WELCOME!", "Welcome message here.");
    return true;
  }
}

// (G) DATABASE SETTINGS - CHANGE TO YOUR OWN!
define('DB_HOST', 'localhost');
define('DB_NAME', 'test');
define('DB_CHARSET', 'utf8');
define('DB_USER', 'root');
define('DB_PASSWORD', '');

// (H) NEW USER OBJECT
$USR = new Users();

 

 

HOW THE USER CORE WORKS

Holy cow. That is a lot of code, but keep calm and look closely.

  • When the $USR = new Users() object is created, the constructor will connect to the database. The destructor closes the connection when done.
  • All that’s left are 4 user functions!
    • function getByEmail() Get user by email.
    • function getById() Get user by user ID.
    • function register() Step 1 of the registration process. Basically generates a random hash, then sent to the user via email for a confirmation.
    • function verify() Step 2 of the registration process. After the user clicks on the link in the email, we verify the random hash and complete the registration process.

 

REGISTRATION PAGES

Now that we have all the groundwork done, all that’s left is creating the actual registration form and confirmation pages.

 

HTML REGISTRATION FORM

3a-register.php
<?php
// (A) PROCESS SUBMITTED REGISTRATION FORM 
$show = true;
if (count($_POST)!=0) {
  require "2-user-core.php";
  $result = $USR->register($_POST['userName'], $_POST['userEmail'], $_POST['userPass'], $_POST['userCpass']);
  if ($result === 1) { $show = false; }
}
 
// (B) SHOW REGISTRATION FORM
if ($show) { 
  if (isset($USR->error) && $USR->error !== null) {
  echo "<div class='error'>$USR->error</div>"; 
} ?>
<h1>REGISTRATION FORM</h1>
<form id="regForm" method="post">
  <label>Name: </label>
  <input type="text" name="userName" placeholder="Full name" required autofocus value="John Doe"><br>

  <label>Email: </label>
  <input type="email" name="userEmail" placeholder="Email" required value="john@doe.com"><br>

  <label>Password: </label>
  <input type="password" name="userPass" placeholder="Password" required value="12345"><br>

  <label>Confirm Password: </label>
  <input type="password" name="userCpass" placeholder="Confirm Password" required value="12345"><br>

  <input type="submit" value="Register"/>
</form>
<?php } 

// (C) SHOW SUCCESSFUL MESSAGE 
else { ?>
<h1>ONE MORE STEP...</h1>
<div>Check your email and click on the link to complete the registration.</div>
<?php } ?>

This is the page where the user will access to do the registration. Yep, it can be a little intimidating at first, but let’s walk through section-by-section.

  • A – This part will only run when the registration form is submitted. Processes the registration using 2-user-core.php, function register().
  • B – The registration form itself. Will also show errors if the registration goes wrong.
  • C – The “check your email to complete registration” message. Will show this when the registration is successful.

 

 

CONFIRMATION PAGE

3b-confirm.php
<?php
require "2-user-core.php";
$status = $USR->verify($_GET['i'], $_GET['h']);
if ($status) { echo "Thank you! Your account has been successfully activated."; }
else { echo $USR->error; }
?>

Finally, this is the landing page that users will land on to complete the registration (click on the confirmation link in the email). Basically, just using the user library function verify() to close things off.

 

USEFUL BITS & LINKS

That’s it for this tutorial. Here are a few small extras and links that may help with your project.

 

BAREBONES START

Before the angry ugly troll things start to spit acid, this is but a barebones example. Some work still needs to be done before it is worthy of a production system.

  • A proper login page – The user session, and checking the user status (links below).
  • An option to resend the confirmation email should the first one fails.
  • Maybe an auto-timeout mechanism, so users can request another confirmation after some time.

 

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end of this guide. I hope that it has helped you with your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

23 thoughts on “Simple User Registration With Email Verification In PHP MYSQL”

  1. Thanks so much. But i encounter a little problem, a confirmation email has been sent after registration but there’s no link to click and verify pls help

    1. Nothing to do with the script, it’s the spam filter. It is also common for most shared hosting to have a low reputation (marked as a possible email spambot).

  2. Hi, awesome tutorial!

    I would like to use PHPMailer for sending the email. Where do I have to implement all the PHPMailer code?

    Many thanks in advance!

      1. Thanks for your kind answer! I certainly read your tutorial and appreciate it.

        I already tried to place the PHPMailer in 2-user-core.php but I was receiving an JSON parse error all the time. Nevermind, I already solved it, it was a typo..

      2. Hello, thank you very much for your tutorial. Your writing is actually very well. However, although I don’t bother to read I couldn’t find anywhere that you mentioned lib/3b-database.php.
        I’m beginner, and I found your tutorial very useful, but simple cannot finish it. The new user is created but I got message Error sending out email.
        So, I’m probably missing that part with lib/3b-database.php.
        Where I can get it from?
        Thank you

      3. Funny how you bothered to read outdated comments, but chose to ignore the entire simplified and updated tutorial.

        3b-database.php is now 2-user-core.php. Also, as above – https://code-boxx.com/fix-php-mail-not-working/

        Well, could have solved the problem a lot faster and lessen the pain if you had the patience to read for 5 minutes. Take this as an extra lesson – Only fools reject knowledge they need the most. 😛

Leave a Comment

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