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
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
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
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
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 withregister()
, 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
<!-- (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
<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
<!-- (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
- Password hash and Password verify – PHP
- How To Import SQL Files – Code Boxx
- Simple PHP MySQL User Login – Code Boxx
- PHP MySQL JWT User Login – Code Boxx
- Problems Sending Out Emails? – Code Boxx
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!
is it possible with PHPMailer in php? I don’t have access to the php.ini to put my smpt server, could you show me how to change the code for phpmailer?
https://code-boxx.com/send-email-php/