Forgot Password with Email OTP Verification using PHP & MySQL

A secure forgot password feature boosts user trust and reduces support tickets. Implementing OTP (one-time password) email verification with PHP ensures only the true account owner can reset the password, adding an extra security layer to your site.

In this tutorial, youโ€™ll learn how to implement a Forgot Password system in PHP that verifies users through an Email OTP (One-Time Password) before allowing them to reset their password.

This is a secure and modern way to let users recover their account access when they forget their password.

๐Ÿš€ Tutorial Overview

Features:

  • Request password reset via email
  • Send a unique OTP to userโ€™s registered email
  • Validate OTP within a limited time
  • Allow password reset after OTP verification
  • Secure password hashing (using password_hash())

Technologies Used:

  • PHP 8+
  • MySQL Database
  • PHPMailer (for sending emails)
  • HTML, CSS (for frontend UI)

๐Ÿ“ Folder Structure

Before getting started to integrate the Forgot Password System with PHP, take a look at the file structure for this tutorial:

forgot-password-with-email-otp/
โ”œโ”€โ”€ config.php
โ”œโ”€โ”€ forgot-password.php
โ”œโ”€โ”€ verify-otp.php
โ”œโ”€โ”€ reset-password.php
โ”œโ”€โ”€ style.css
โ””โ”€โ”€ PHPMailer/

PHPMailer Library

This tutorial uses the PHPMailer library to send OTP emails. You can download it from its official GitHub repository or install it via Composer:

composer require phpmailer/phpmailer

Note: All the required PHPMailer files are already included in our ready-to-download source code ZIP package. You don’t need to download or install it separately.

Step 1: Create Database and Table

Create a MySQL database (e.g., user_auth_db) and a users table to store user information, including email, password, and created_at timestamp.

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Step 2: Configure Database and SMTP Settings

Create a config.php file to store database connection and SMTP email settings.

  • Update the database configuration variables ($dbHost, $dbUsername, $dbPassword, $dbName) with your database credentials.
  • Set the SMTP email configuration variables ($smtpHost, $smtpUsername, $smtpPassword, $smtpPort, $smtpFromEmail, $smtpFromName) with your SMTP server details.
<?php 
// Database configuration
$dbHost "localhost";
$dbUsername "root";
$dbPassword "root_pass";
$dbName "user_auth_db";

// Create database connection
$conn = new mysqli($dbHost$dbUsername$dbPassword$dbName);

// Check connection
if ($conn->connect_error) {
    die(
"Connection failed: " $conn->connect_error);
}

// Email configuration
$smtpHost 'your.smtp.server';
$smtpUsername 'your@email.com';
$smtpPassword 'your-smtp-password';
$smtpPort 587;
$smtpFromEmail 'noreply@yourdomain.com';
$smtpFromName 'Your App';

// Start session
if(session_status() == PHP_SESSION_NONE){
    
session_start();
}
?>

Step 3: Create Forgot Password Form

Create a forgot-password.php file with a form to collect the user’s email address.

The PHP code at the top of the file handles the following:

  • Includes the configuration file.
  • Imports PHPMailer classes.
  • Handles form submission to check if the email exists in the database, generates an OTP, stores it in the session, and sends the OTP email.
  • Redirects to the OTP verification page (verify-otp.php) upon successful email sending.
<?php 
// Include configuration file
require_once 'config.php';

// Import PHPMailer classes into the global namespace
use PHPMailer\PHPMailer\PHPMailer;
use 
PHPMailer\PHPMailer\Exception;
require 
'PHPMailer/Exception.php';
require 
'PHPMailer/PHPMailer.php';
require 
'PHPMailer/SMTP.php';

// Handle form submission
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    
$email $_POST['email'];
    
    
// Check if email exists in database
    
$stmt $conn->prepare("SELECT id FROM users WHERE email = ?");
    
$stmt->bind_param("s"$email);
    
$stmt->execute();
    
$result $stmt->get_result();
    
    if (
$result->num_rows 0) {
        
// Generate OTP
        
$otp rand(100000999999);
        
$_SESSION['reset_otp'] = $otp;
        
$_SESSION['reset_email'] = $email;
        
$_SESSION['otp_time'] = time();
        
        
// Create a new PHPMailer instance
        
$mail = new PHPMailer(true);
        
        try {
            
// Server settings
            
$mail->isSMTP();
            
$mail->Host $smtpHost;
            
$mail->SMTPAuth true;
            
$mail->Username $smtpUsername;
            
$mail->Password $smtpPassword;
            
$mail->SMTPSecure PHPMailer::ENCRYPTION_STARTTLS;
            
$mail->Port $smtpPort;
            
            
// Recipients
            
$mail->setFrom($smtpFromEmail$smtpFromName);
            
$mail->addAddress($email);
            
            
// Content
            
$mail->isHTML(true);
            
$mail->Subject 'Password Reset OTP';
            
$mail->Body "Your OTP for password reset is: <b>$otp</b><br>This OTP will expire in 10 minutes.";
            
            if (
$mail->send()) {
                
// Set a session status message so verify-otp.php can show it after redirect
                
$_SESSION['status'] = "An OTP has been sent to your email address. Please check your inbox (and spam).";
                
header("Location: verify-otp.php");
                exit();
            } else {
                
$error "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
            }
        } catch (
Exception $e) {
            
$error "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
        }
    } else {
        
$error "Email address not found!";
    }
}
?> <form method="POST" class="form"> <h2>Forgot Password</h2> <?php if (isset($error)) { ?> <div class="error"><?php echo $error?></div> <?php ?> <div class="form-group"> <label for="email">Email Address</label> <input type="email" name="email" id="email" required> </div> <button type="submit">Send OTP</button> <div class="links"> <a href="login.php">Back to Login</a> </div> </form>

Step 4: Create OTP Verification Form

Create a verify-otp.php file with a form to collect the OTP from the user.

The PHP code at the top of the file handles the following:

  • Includes the configuration file.
  • Checks if the OTP and email are set in the session; if not, redirects to the forgot password page.
  • Captures and clears any status message set by the previous step.
  • Handles form submission to verify the entered OTP against the stored OTP and checks for expiration (10 minutes validity). If valid, redirects to the reset password page (reset-password.php).
<?php 
// Include configuration file
require_once 'config.php';

// Check if OTP and email are set in session
if (!isset($_SESSION['reset_otp']) || !isset($_SESSION['reset_email'])) {
    
header("Location: forgot-password.php");
    exit();
}

// Capture and clear any status message set by previous step
$status null;
if (isset(
$_SESSION['status'])) {
    
$status $_SESSION['status'];
    unset(
$_SESSION['status']);
}

// Handle form submission
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    
$entered_otp $_POST['otp'];
    
$stored_otp $_SESSION['reset_otp'];
    
$otp_time $_SESSION['otp_time'];
    
    
// Check if OTP is expired (10 minutes validity)
    
if (time() - $otp_time 600) {
        
$error "OTP has expired. Please request a new one.";
        unset(
$_SESSION['reset_otp']);
        unset(
$_SESSION['otp_time']);
    }
    
// Verify OTP
    
else if ($entered_otp == $stored_otp) {
        
$_SESSION['otp_verified'] = true;
        
header("Location: reset-password.php");
        exit();
    } else {
        
$error "Invalid OTP. Please try again.";
    }
}
?> <form method="POST" class="form"> <h2>Verify OTP</h2> <?php if (isset($error)) { ?> <div class="error"><?php echo $error?></div> <?php ?> <?php if (isset($status)) { ?> <div class="success"><?php echo $status?></div> <?php ?> <div class="form-group"> <label for="otp">Enter OTP</label> <input type="number" name="otp" id="otp" required> </div> <button type="submit">Verify OTP</button> <div class="links"> <a href="forgot-password.php">Request New OTP</a> </div> </form>

Step 5: Create Password Reset Form

Create a reset-password.php file with a form to allow the user to reset their password.

The PHP code at the top of the file handles the following:

  • Includes the configuration file.
  • Checks if the OTP is verified; if not, redirects to the forgot password page.
  • Handles form submission to update the user’s password in the database after hashing it using PHP password_hash() function.
  • Upon success, it clears the session variables and redirects to the login page with a success message.
<?php 
// Include configuration file
require_once 'config.php';

// Check if OTP is verified
if (!isset($_SESSION['otp_verified']) || !isset($_SESSION['reset_email'])) {
    
header("Location: forgot-password.php");
    exit();
}

// Handle form submission
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    
$password $_POST['password'];
    
$confirm_password $_POST['confirm_password'];
    
    if (
$password !== $confirm_password) {
        
$error "Passwords do not match!";
    } else {
        
$email $_SESSION['reset_email'];
        
$hashed_password password_hash($passwordPASSWORD_DEFAULT);
        
        
// Update password in database
        
$stmt $conn->prepare("UPDATE users SET password = ? WHERE email = ?");
        
$stmt->bind_param("ss"$hashed_password$email);
        
        if (
$stmt->execute()) {
            
// Clear all session variables
            
session_unset();
            
session_destroy();
            
            
header("Location: login.php?reset=success");
            exit();
        } else {
            
$error "Error updating password. Please try again.";
        }
    }
}
?> <form method="POST" class="form"> <h2>Reset Password</h2> <?php if (isset($error)) { ?> <div class="error"><?php echo $error?></div> <?php ?> <div class="form-group"> <label for="password">New Password</label> <input type="password" name="password" id="password" required> </div> <div class="form-group"> <label for="confirm_password">Confirm Password</label> <input type="password" name="confirm_password" id="confirm_password" required> </div> <button type="submit">Reset Password</button> </form>

๐Ÿง  Best Practices & Security Tips

  • Always hash passwords using password_hash() (never store plain text passwords).
  • Use prepared statements to prevent SQL injection in MySQL database.
  • Use App Passwords for Gmail SMTP (never use your actual password).
  • Set OTP expiry time (e.g., 10 minutes).
  • Add rate limiting (optional) to prevent OTP abuse.

Conclusion

You have successfully created a Forgot Password with Email OTP Verification system using PHP and MySQL. This secure approach ensures only verified users can reset their passwords and helps prevent unauthorized access.

This script can be further enhanced by integrating it into a complete user authentication system. For a full registration and login system, you can refer to this comprehensive tutorial: Secure Registration and Login System with PHP and MySQL

Make sure to test the entire flow thoroughly and adapt the code to fit your application’s structure and security requirements. Happy coding! ๐Ÿš€

Looking for expert assistance to implement or extend this scriptโ€™s functionality? Submit a Service Request

Leave a reply

construction Need this implemented in your project? Request Implementation Help โ†’ keyboard_double_arrow_up