Here you will learn how to verify new users’ email through OTP using PHP.
I have already shared a project of user login and registration system, and here we will implement email verification in this system.
We will be using PHPMailer to send verification emails, so please checkout – How to use PHPMailer?
PHP Login & Registration with email verification

The following image shows what files we have to create to build this PHP Login & Registration with email verification application –

1. Database Setup
- Create Database
- DB Connection
- Database Name –
php_login_verification
- Table Name –
users
Use the following SQL code to create the users
table and its columns.
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL,
`email` varchar(30) NOT NULL,
`password` varchar(150) NOT NULL,
`verified` int(1) NOT NULL DEFAULT 0,
`verification_code` tinytext DEFAULT NULL,
`otp` int(8) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci;
<?php
$db_host = 'localhost';
$db_user = 'root';
$db_password = '';
$db_name = 'php_login_verification';
$conn = new mysqli($db_host, $db_user, $db_password, $db_name);
// CHECK DATABASE CONNECTION
if($conn->error){
echo "Connection Failed - ".$db_connection->connect_error;
exit;
}
2. Mail.php & style.css
First, install the PHPMailer via the composer, because we are going to use this package in the Mail.php
class to send verification emails.
composer require phpmailer/phpmailer
- Mail.php
- style.css
The send()
method of the Mail
class is for sending emails through PHPMailer. Change protected $pass
and protected $sender_email
according to yours.
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
require __DIR__ . "/vendor/autoload.php";
class Mail
{
protected $pass = "***your_app_password***";
protected $sender_email = "***[email protected]****";
protected $mailer;
function __construct()
{
$this->mailer = new PHPMailer(true);
}
function send($receiver_email, $otp)
{
try {
$this->mailer->isSMTP();
$this->mailer->SMTPDebug = SMTP::DEBUG_OFF;
$this->mailer->Host = 'smtp.gmail.com';
$this->mailer->Port = 587;
$this->mailer->SMTPSecure = "TLS";
$this->mailer->SMTPAuth = true;
$this->mailer->Username = $this->sender_email;
$this->mailer->Password = $this->pass;
$this->mailer->setFrom($this->sender_email, 'OTP from PHP Login');
$this->mailer->addAddress($receiver_email);
$this->mailer->isHTML(true);
$this->mailer->Subject = "OPT for email verification.";
$this->mailer->Body = "Your OTP is - $otp valid for 10 minutes.";
$this->mailer->send();
return true;
} catch (Exception $e) {
// echo "Message could not be sent. Mailer Error: {$this->mailer->ErrorInfo}";
return false;
}
}
}
@import url("https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;700&display=swap");
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
font-size: 16px;
}
body {
background-color: #f7f7f7;
font-family: "Ubuntu", sans-serif;
margin: 0;
padding: 0;
color: #222222;
overflow-x: hidden;
overflow-wrap: break-word;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
padding: 50px;
}
.container {
background-color: white;
border-radius: 3px;
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175);
margin: 0 auto;
max-width: 450px;
padding: 40px;
}
.container h1 {
margin: 0 0 40px 0;
text-align: center;
}
input,
button {
font-family: "Ubuntu", sans-serif;
font-size: 1rem;
outline: none;
}
.input {
padding: 15px;
width: 100%;
margin-bottom: 15px;
border: 1px solid #bbbbbb;
border-radius: 3px;
}
.input:hover {
border-color: #999999;
}
.input:focus {
border-color: #0d6efd;
}
.input.error {
border-color: red !important;
}
label span {
color: red;
}
.msg {
border: 1px solid #66ba7a;
background: #f3ffd1;
padding: 10px;
border-radius: 3px;
}
.msg.error {
border-color: #e33b54;
background: #f9d7dc;
}
[type="submit"] {
background: #0d6efd;
color: white;
border: 1px solid rgba(0, 0, 0, 0.175);
border-radius: 3px;
padding: 12px 0;
cursor: pointer;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
margin-top: 5px;
font-weight: bold;
width: 100%;
}
[type="submit"]:hover {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
label {
font-weight: bold;
display: inline-block;
margin-bottom: 3px;
}
.link {
margin-top: 20px;
text-align: center;
}
.link a {
color: #0d6efd;
}
.profile {
text-align: center;
}
.profile img {
display: inline-block;
border: 3px solid #ccc;
border-radius: 50%;
width: 150px;
height: 150px;
}
h2 span {
display: block;
font-size: 15px;
font-weight: 400;
color: #888;
}
3. New User Registration
To register a new user we have to create the register.php
and on_register.php
.
on_register.php
contains the on_register()
function which contains the code for inserting new users, and this on_register()
function will be called when the sign up form is submitted.
In the on_register()
function you can see – after inserting a new user an OTP will be sent to the user’s email for verification.
- register.php
- on_register.php
<?php
session_start();
if (isset($_SESSION['logged_user_id'])) {
header('Location: home.php');
exit;
}
if ($_SERVER["REQUEST_METHOD"] === "POST") :
require_once __DIR__ . "/db_connection.php";
require_once __DIR__ . "/on_register.php";
if (
isset($conn) &&
isset($_POST["name"]) &&
isset($_POST["email"]) &&
isset($_POST["password"])
) {
$result = on_register($conn);
}
endif;
// If the user is registered successfully, don't show the post values.
$show = isset($result["form_reset"]) ? true : false;
function post_value($field)
{
global $show;
if (isset($_POST[$field]) && !$show) {
echo 'value="' . trim(htmlspecialchars($_POST[$field])) . '"';
return;
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sign Up</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container">
<h1>Sign Up</h1>
<form action="" method="POST" id="theForm">
<label for="user_name">Name: <span></span></label>
<input type="text" class="input" name="name" <?php post_value("name"); ?> id="user_name" placeholder="Your name">
<label for="user_email">Email: <span></span></label>
<input type="email" class="input" name="email" <?php post_value("email"); ?> id="user_email" placeholder="Your email">
<label for="user_pass">Password: <span></span></label>
<input type="password" class="input" name="password" <?php post_value("password"); ?> id="user_pass" placeholder="New password">
<?php if (isset($result["msg"])) { ?>
<p class="msg<?php if ($result["ok"] === 0) {
echo " error";
} ?>"><?php echo $result["msg"]; ?></p>
<?php } ?>
<input type="submit" value="Sign Up">
<div class="link"><a href="./login.php">Login</a></div>
</form>
</div>
<script>
<?php
if (isset($result["field_error"])) { ?>
let field_error = <?php echo json_encode($result["field_error"]); ?>;
let el = null;
let msg_el = null;
for (let i in field_error) {
el = document.querySelector(`input[name="${i}"]`);
el.classList.add("error");
msg_el = document.querySelector(`label[for="${el.getAttribute("id")}"] span`);
msg_el.innerText = field_error[i];
}
<?php } ?>
</script>
</body>
</html>
<?php
require __DIR__ . "/Mail.php";
function on_register($conn)
{
$name = htmlspecialchars(trim($_POST['name']));
$email = trim($_POST['email']);
$pass = trim($_POST['password']);
if (empty($name) || empty($email) || empty($pass)) {
$arr = [];
if (empty($name)) $arr["name"] = "Must not be empty.";
if (empty($email)) $arr["email"] = "Must not be empty.";
if (empty($pass)) $arr["password"] = "Must not be empty.";
return [
"ok" => 0,
"field_error" => $arr
];
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return [
"ok" => 0,
"field_error" => [
"email" => "Invalid email address."
]
];
}
if (strlen($pass) < 4) {
return [
"ok" => 0,
"field_error" => [
"password" => "Must be at least 4 characters."
]
];
}
// CHECK IF EMAIL IS ALREADY REGISTERED
$sql = "SELECT `email` FROM `users` WHERE `email` = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $email);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows !== 0) {
return [
"ok" => 0,
"field_error" => [
"email" => "This Email is already registered."
]
];
}
$pass = password_hash($pass, PASSWORD_DEFAULT);
$otp = rand(100000, 999999);
$token = [
"email" => $email,
"expiry" => time()
];
$token = base64_encode(serialize($token));
$sql = "INSERT INTO `users` (`name`, `email`, `password`,`verification_code`,`otp`) VALUES (?,?,?,?,?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ssssi", $name, $email, $pass, $token, $otp);
$is_inserted = $stmt->execute();
if ($is_inserted) {
// Sending OTP to the mail
$mail = new Mail();
$send_email = $mail->send($email, $otp);
if ($send_email) {
header("Location: verify.php?token=$token");
exit;
}
return [
"ok" => 1,
"msg" => "You have been registered successfully, But failed to send the verification email.",
"form_reset" => true
];
}
return [
"ok" => 0,
"msg" => "Something going wrong!"
];
}
4. Email Verification through OTP
After a user is successfully registered, the user will automatically be redirected to verify.php
with a unique token
.
/verify.php?token=YToyOntzOjU6ImVtYWlsIjtzOjIwOiJoZWxsb3dvcmxkQGdtYWlsLmNvbSI7czo2OiJleHBpcnkiO2k6MTY5MDA5OTI2OTt9
The base64
encoded token will contain an array containing the user’s email and token expiration time.
- verify.php
- otp_verify.php
- resend_otp.php
<?php
if (!isset($_GET["token"]) || !base64_decode($_GET["token"], true)) {
header("Location: login.php");
exit;
}
$encoded_token = $_GET["token"];
$token = unserialize(base64_decode($_GET["token"], true));
if (!is_array($token) || !isset($token["email"]) || !filter_var($token["email"], FILTER_VALIDATE_EMAIL)) {
header("Location: logout.php");
exit;
}
$error = NULL;
$is_valid_otp = NULL;
require_once __DIR__ . "/db_connection.php";
$sql = "SELECT `id`,`verification_code`,`otp` FROM `users` WHERE `email` = ? AND `verified`=0";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $token["email"]);
$stmt->execute();
$data = $stmt->get_result();
$row = $data->fetch_array(MYSQLI_ASSOC);
// DB verification code and URL token are equal or not
if ($row === NULL || $row["verification_code"] !== $encoded_token) {
header("Location: logout.php");
exit;
}
// Checking the Token Expiry
if ((time() - $token["expiry"]) > 600) {
$error = "Your previous OTP has expired, <strong>resend a new OTP</strong> to verify your email.";
}
// IF OTP is Submitted, verify the OTP
if ($error === NULL && $_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["otp"])) {
require_once __DIR__ . "/otp_verify.php";
$is_valid_otp = otp_verify($conn, $row["id"], $row["otp"], $_POST["otp"]);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email verification</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container">
<h1>Enter OTP</h1>
<?php if ($error !== NULL) { ?>
<p class="msg error"><?php echo $error; ?></p>
<div class="link"><a href="./resend_otp.php?token=<?php echo $encoded_token; ?>">Resend</a></div>
<?php } elseif ($is_valid_otp === true) { ?>
<p class="msg">Your <strong>email</strong> has been <strong>successfully verified</strong>.</p>
<div class="link"><a href="./login.php">Login</a></div>
<?php } else { ?>
<form action="" method="POST">
<p>Please <strong>enter the OTP</strong> which has been sent to your email for verification.</p>
<label for="otp">Your OTP: <span><?php echo ($is_valid_otp === false) ? "Invalid OTP" : ""; ?></span></label>
<input type="text" class="input<?php echo ($is_valid_otp === false) ? " error" : ""; ?>" name="otp" id="otp" placeholder="OTP">
<input type="submit" value="Verify">
<div class="link"><a href="./resend_otp.php?token=<?php echo $encoded_token; ?>">Resend</a></div>
</form>
<?php } ?>
</div>
</body>
</html>
<?php
function otp_verify($conn, $id, $db_otp, $user_otp)
{
if ($db_otp == $user_otp) {
$sql = "UPDATE `users` SET `verified`=1 WHERE `id`=$id";
$conn->query($sql);
if ($conn->query($sql)) {
return true;
}
return false;
}
return false;
}
<?php
if (!isset($_GET["token"]) || !base64_decode($_GET["token"], true)) {
header("Location: login.php");
exit;
}
$token = unserialize(base64_decode($_GET["token"], true));
if (!is_array($token) || !isset($token["email"]) || !filter_var($token["email"], FILTER_VALIDATE_EMAIL)) {
header("Location: logout.php");
exit;
}
require_once __DIR__ . "/db_connection.php";
require_once __DIR__ . "/Mail.php";
$sql = "SELECT `id` FROM `users` WHERE `email` = ? AND `verified`=0";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $token["email"]);
$stmt->execute();
$data = $stmt->get_result();
$row = $data->fetch_array(MYSQLI_ASSOC);
if ($row === NULL) {
header("Location: logout.php");
exit;
}
$userID = $row["id"];
$otp = rand(100000, 999999);
$new_token = [
"email" => $token["email"],
"expiry" => time()
];
$new_token = base64_encode(serialize($new_token));
$sql = "UPDATE `users` SET `verification_code`=?, `otp`=$otp WHERE `id`=$userID";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $new_token);
$mail = new Mail();
$send_email = $mail->send($token["email"], $otp);
if ($send_email && $stmt->execute()) {
header("Location: verify.php?token=$new_token");
exit;
}
header("Location: verify.php?token=$token");
exit;
5. User Login
login.php
and on_login.php
contain the code to login for existing users whose email is verified. The on_login()
function will be called on submission of the login form.
- login.php
- on_login.php
<?php
session_start();
if(isset($_SESSION['logged_user_id'])){
header('Location: home.php');
exit;
}
if ($_SERVER["REQUEST_METHOD"] === "POST") :
require_once __DIR__ . "/db_connection.php";
require_once __DIR__."/on_login.php";
if (isset($conn) && isset($_POST["email"]) && isset($_POST["password"])) {
$result = on_login($conn);
}
endif;
function post_value($field){
if(isset($_POST[$field])){
echo 'value="'.trim(htmlspecialchars($_POST[$field])).'"';
return;
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Page</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container">
<h1>Login</h1>
<form action="" method="POST">
<label for="user_email">Email: <span></span></label>
<input type="email" class="input" name="email" <?php post_value("email"); ?> id="user_email" placeholder="Your email">
<label for="user_pass">Password: <span></span></label>
<input type="password" class="input" name="password" <?php post_value("password"); ?> id="user_pass" placeholder="Your password">
<input type="submit" value="Login">
<div class="link"><a href="./register.php">Sign Up</a></div>
</form>
</div>
<script>
<?php
if(isset($result["field_error"])){ ?>
let field_error = <?php echo json_encode($result["field_error"]); ?>;
let el = null;
let msg_el = null;
for(let i in field_error){
el = document.querySelector(`input[name="${i}"]`);
el.classList.add("error");
msg_el = document.querySelector(`label[for="${el.getAttribute("id")}"] span`);
msg_el.innerText = field_error[i];
}
<?php } ?>
</script>
</body>
</html>
<?php
function on_login($conn)
{
$email = trim($_POST['email']);
$pass = trim($_POST['password']);
if (empty($email) || empty($pass)) {
$arr = [];
if (empty($email)) $arr["email"] = "Must not be empty.";
if (empty($pass)) $arr["password"] = "Must not be empty.";
return [
"ok" => 0,
"field_error" => $arr
];
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return [
"ok" => 0,
"field_error" => [
"email" => "Invalid email address."
]
];
}
$sql = "SELECT * FROM `users` WHERE `email` = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $email);
$stmt->execute();
$data = $stmt->get_result();
$row = $data->fetch_array(MYSQLI_ASSOC);
if ($row === NULL) {
return [
"ok" => 0,
"field_error" => [
"email" => "This email is not registered."
]
];
}
if ($row["verified"] === 0) {
header("Location: verify.php?token={$row["verification_code"]}");
exit;
}
$password_check = password_verify($pass, $row["password"]);
if ($password_check === false) {
return [
"ok" => 0,
"field_error" => [
"password" => "Incorrect Password."
]
];
}
$_SESSION['logged_user_id'] = $row["id"];
header('Location: home.php');
exit;
}
6. After login successfully
After login successfully, the user will be redirected to the home.php
and the get_user.php
will fetch the user information from the database.
- home.php
- get_user.php
<?php
session_start();
session_regenerate_id(true);
if (!isset($_SESSION['logged_user_id']) || empty($_SESSION['logged_user_id']) || !is_numeric($_SESSION['logged_user_id'])) {
header('Location: logout.php');
exit;
}
require_once __DIR__ . "/db_connection.php";
require_once __DIR__ . "/get_user.php";
// Get the User by ID that stored in the session
$user = get_user($conn, $_SESSION['logged_user_id']);
// If User is Empty
if ($user === false) {
header('Location: logout.php');
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container">
<div class="profile">
<img src="https://robohash.org/set_set3/<?php echo $user["id"]; ?>?size=200x200" alt="<?php echo $user["name"]; ?>">
<h2><?php echo $user["name"]; ?><span><?php echo $user["email"]; ?></span></h2>
<a href="./logout.php">Log out</a>
</div>
</div>
</body>
</html>
<?php
function get_user($conn, $id)
{
if (!filter_var($id, FILTER_VALIDATE_INT)) {
return false;
}
$sql = "SELECT * FROM `users` WHERE `id` = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $id);
$stmt->execute();
$data = $stmt->get_result();
$row = $data->fetch_array(MYSQLI_ASSOC);
if ($row === NULL) return false;
return $row;
}
7. Logout the logged-in user
<?php
// Initialize the session.
// If you are using session_name("something"), don't forget it now!
session_start();
// Unset all of the session variables.
$_SESSION = array();
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params["path"],
$params["domain"],
$params["secure"],
$params["httponly"]
);
}
// Finally, destroy the session.
session_destroy();
header("Location: login.php");
exit;
Thank You … ❤️❤️❤️