Simple User Registration With Email Verification In PHP MYSQL

Welcome to a tutorial on how to create a user registration system with email verification, in 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:

  • A database to store the users.
  • A PHP library to deal with the registration process.
  • An HTML registration form and the confirmation page itself.

Let us walk through the details in this guide – Read on!

 

 

TABLE OF CONTENTS

 

DOWNLOAD & NOTES

Here is the download link to the example code, so you don’t have to copy-paste everything.

 

EXAMPLE CODE DOWNLOAD

Click here to download

The example code is released under the MIT license, so feel free to build on top of it or use it in your own project.

 

SORRY FOR THE ADS...

But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.

Buy Me A Coffee Code Boxx eBooks

 

 

USER REGISTRATION WITH EMAIL VERIFICATION

All right, let us now get into the steps of creating a simple user registration system with email verification.

 

TUTORIAL VIDEO

 

PART 1) DATABASE

1A) USERS TABLE

1-users.sql
CREATE TABLE `users` (
  `id` bigint(20) NOT NULL,
  `email` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

ALTER TABLE `users`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `email` (`email`);
 
ALTER TABLE `users` MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
Field Description
id User ID. Primary key and auto-increment.
email The user’s email address. Unique field to prevent multiple registrations.
password User’s login password.

This is just a simple user table, feel free to add more fields as required – Name, address, etc…

 

1B) ACTIVATION HASH TABLE

1-users.sql
CREATE TABLE `users_hash` (
  `id` bigint(20) NOT NULL,
  `time` datetime NOT NULL DEFAULT current_timestamp(),
  `hash` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
ALTER TABLE `users_hash`
  ADD PRIMARY KEY (`id`);
Field Description
id User ID. Primary and foreign key.
time Timestamp of when the security hash is created.
hash Pretty much just a random string.

Now, we will send a validation link to the user after registration. The link will need to include a hash (random string) for security – This table is used to store that security hash.

 

 

PART 2) PHP USER LIBRARY

2-lib-user.php
class Users {
  // (A) CONSTRUCTOR - CONNECT DATABASE
  private $pdo = null;
  private $stmt = null;
  public $error = null;
  function __construct () {
    $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
    ]);
  }

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

  // (C) HELPER - RUN SQL QUERY
  function query ($sql, $data=null) : void {
    $this->stmt = $this->pdo->prepare($sql);
    $this->stmt->execute($data);
  }
 
  // (D) HELPER - GET USER BY ID OR EMAIL
  function get ($id) {
    $this->query(sprintf(
      "SELECT * FROM `users`
      LEFT JOIN `users_hash` USING (`id`)
      WHERE users.`%s`=?",
      is_numeric($id) ? "id" : "email"
    ), [$id]);
    return $this->stmt->fetch();
  }

  // (E) REGISTER NEW USER
  function register ($email, $pass) {
    // (E1) CHECK IF USER REGISTERED
    $user = $this->get($email);
    if (is_array($user)) {
      $this->error = "Already registered.";
      return false;
    }
 
    // (E2) CREATE ACCOUNT
    $this->query(
      "INSERT INTO `users` (`email`, `password`) VALUES (?,?)",
      [$email, password_hash($pass, PASSWORD_DEFAULT)]
    );
    $this->lastID = $this->pdo->lastInsertId();
 
    // (E3) ACTIVATION EMAIL
    return $this->activation($email, $this->lastID);
  }
 
  // (F) SEND ACTIVATION EMAIL
  // * register pass in both email and id
  // * resend pass in email only
  function activation ($email, $id=null) {
    // (F1) RESEND ONLY - CHECKS
    if ($id === null) {
      $user = $this->get($email);
      if (!is_array($user)) {
        $this->error = "Invalid user.";
        return false;
      }
      if (!isset($user["hash"])) {
        $this->error = "Already registered.";
        return false;
      }
      $remain = (strtotime($user["time"]) + HASH_VALID) - strtotime("now");
      if ($remain > 0) {
        $this->error = "Please wait another $remain seconds.";
        return false;
      }
      $id = $user["id"];
    }
 
    // (F2) CREATE HASH
    $hash = md5(date("YmdHis") . $email);
    $this->query(
      "REPLACE INTO `users_hash` (`id`, `hash`) VALUES (?,?)",
      [$id, $hash]
    );
 
    // (F3) SEND LINK TO USER
    if (@mail(
      $email, "Confirm your email",
      sprintf(
        "<a href='http://localhost/4-confirm.php?i=%u&h=%s'>Click here</a> to complete the registration.",
        $id, $hash
      ),
      implode("\r\n", ["MIME-Version: 1.0", "Content-type: text/html; charset=utf-8"])
    )) { return true; }
    $this->error = "Error sending out email";
    return false;
  }
 
  // (G) VERIFY REGISTRATION
  function verify ($id, $hash) {
    // (G1) GET + CHECK THE USER
    $user = $this->get($id);
    if ($user === false) {
      $this->error = "Invalid acivation link.";
      return false;
    }
    if (!isset($user["hash"])) {
      $this->error = "Already activated.";
      return false;
    }
    if (strtotime("now") > strtotime($user["time"]) + HASH_VALID) {
      $this->error = "Activation link expired.";
      return false;
    }
    if ($user["hash"] != $hash) {
      $this->error = "Invalid activation link.";
      return false;
    }
 
    // (G2) ACTIVATE ACCOUNT IF OK
    $this->query(
      "DELETE FROM `users_hash` WHERE `id`=?",
      [$id]
    );
    return true;
  }
}

// (H) DATABASE + VALIDATION SETTINGS - CHANGE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8mb4");
define("DB_USER", "root");
define("DB_PASSWORD", "");
define("HASH_VALID", 600); // 10 mins = 600 seconds

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

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

  • (A, B, I) When $USR = new Users() is created, the constructor will connect to the database. The destructor closes the connection.
  • (C, D) Helper functions.
    • query() Helper function to run an SQL query.
    • get() Helper function to get a user from the database.
  • (E) register() Creates a user account in the database.
  • (F) activation() Follows up with register(), sends an account activation link to the user.
    • Generates a random hash (string), and saves it into the database.
    • Sends the activation link to the user via email – http://site.com/4-confirm.php?i=USER-ID&h=RANDOM-HASH
    • Take note, this function can also used to resend the activation link.
  • (G) verify() After the user clicks on the link in the email, use this function to verify the hash and complete the registration process.
  • (H) Database settings. Remember to change to your own.

 

 

PART 3) REGISTRATION PAGE

3-register.php
<!-- (A) REGISTRATION FORM -->
<form method="post">
  <input type="email" name="email" placeholder="Email" required>
  <input type="password" name="pass" placeholder="Password" required>
  <input type="submit" value="Register!">
</form>
 
<?php
// (B) PROCESS SUBMITTED REGISTRATION FORM
if (isset($_POST["email"])) {
  require "2-lib-user.php";
  echo "<div class='note'>";
  echo $USR->register($_POST["email"], $_POST["pass"])
    ? "Check your email and click on the activation link"
    : $USR->error ;
  echo "</div>";
}
?>

This page is pretty self-explanatory. The user fills in the registration form, we use register() to process and send out the activation link.

 

 

PART 4) CONFIRMATION PAGE

4-confirm.php
<div class="note"><?php
require "2-lib-user.php";
echo $USR->verify($_GET["i"], $_GET["h"])
  ? "Thank you! Account has been activated."
  : $USR->error ;
?></div>

The user accesses the activation link and lands on this page – We verify the hash and complete the process.

 

 

PART 5) RESEND THE ACTIVATION LINK

5-resend.php
<!-- (A) RESEND FORM -->
<form method="post">
  <input type="email" name="email" placeholder="Email" required>
  <input type="submit" value="Resend!">
</form>
 
<?php
// (B) PROCESS SUBMITTED FORM
if (isset($_POST["email"])) {
  require "2-lib-user.php";
  echo "<div class='note'>";
  echo $USR->activation($_POST["email"])
    ? "Check your email and click on the activation link"
    : $USR->error ;
  echo "</div>";
}
?>

Well, if users need to resend the activation link – Here it is.

 

 

EXTRAS

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

 

HOW ABOUT LOGIN?

function login ($email, $password) {
  // GET & CHECK USER
  $user = $this->get($email);
  if (!is_array($user) || isset($user["hash"])) {
    $this->error = "Invalid user/password.";
    return false;
  }

  // VALIDATE PASSWORD
  if (password_verify($password, $user["password"])) {
    // OK - START SESSION
  } else {
    $this->error = "Invalid user/password.";
    return false;
  }
}

A small extra bit for the beginners, just add another login function to the library. But “user login and session” is another story – Whether you use the “traditional” PHP session, JWT, or your own “special mechanism”. Follow up with the links below if you want to learn more.

 

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!

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

  1. 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 *