Project Overview

A Password Reset System allows users to securely reset their forgotten passwords by verifying their identity through email. This project covers user authentication, token generation, email sending, and secure password updating using PHP and MySQL.

Prerequisites

Ensure you have the following:

  • Web Server: Apache (using XAMPP, WAMP, or MAMP)
  • PHP: Version 7.4 or higher
  • MySQL: For database management
  • Code Editor: VS Code, Sublime Text, PHPStorm, etc.
  • Composer: For dependency management
  • SMTP Server or Email Service: For sending emails (e.g., Gmail SMTP, SendGrid)
  • Basic Understanding of Sessions, Forms, and Email Sending in PHP

Step-by-Step Procedure

1. Setting Up the Development Environment

  1. Install XAMPP:
    • Download from XAMPP Official Website.
    • Follow the installation wizard and install it in the default directory.
  2. Start Apache and MySQL:
    • Open the XAMPP Control Panel.
    • Start the Apache and MySQL modules.
  3. Install Composer:

2. Creating the Database

  1. Access phpMyAdmin:
    • Navigate to http://localhost/phpmyadmin/ in your browser.
  2. Create a New Database:
    • Click on “New” in the left sidebar.
    • Name the database password_reset_system.
    • Choose “utf8mb4_unicode_ci” as the collation.
    • Click “Create”.
  3. Create a users Table:
    • Select the password_reset_system database.
    • Click on “New” to create a table.
    • Define the table with the following fields:
    Field Type Null Key Default Extra id INT NO PRI NULL AUTO_INCREMENT username VARCHAR(50) NO UNI NULL email VARCHAR(100) NO UNI NULL password VARCHAR(255) NO NULL reset_token VARCHAR(255) YES UNI NULL token_expiry DATETIME YES NULL created_at TIMESTAMP NO CURRENT_TIMESTAMP
    • Click “Save”.

3. Project Structure

Organize your project files as follows:

password-reset-system/
├── vendor/             # Composer dependencies
├── assets/
│   ├── css/
│   │   └── styles.css
│   └── js/
│       └── scripts.js
├── config/
│   └── db.php
├── templates/
│   ├── header.php
│   └── footer.php
├── register.php
├── login.php
├── forgot_password.php
├── reset_password.php
├── send_email.php
├── README.md
└── composer.json

4. Installing PHPMailer via Composer

PHPMailer simplifies sending emails via SMTP.

  1. Navigate to Project Directory:
    • Open your terminal or command prompt.
    • Navigate to password-reset-system directory.
  2. Install PHPMailer: composer require phpmailer/phpmailer

5. Configuration

a. Database Connection (config/db.php)

Create a file named db.php inside the config directory to handle database connections.

<?php
// config/db.php

$host = 'localhost';
$db   = 'password_reset_system';
$user = 'root'; // Default XAMPP MySQL user
$pass = '';     // Default XAMPP MySQL password is empty
$charset = 'utf8mb4';

$dsn = "mysql:host=$host;dbname=$db;charset=$charset";

$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION, // Enable exceptions
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,       // Fetch associative arrays
    PDO::ATTR_EMULATE_PREPARES   => false,                  // Disable emulation
];

try {
    $pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
    // Handle connection errors
    http_response_code(500);
    echo "Database connection failed.";
    exit;
}
?>
b. Email Configuration (send_email.php)

Create a file named send_email.php to handle email sending using PHPMailer.

<?php
// send_email.php

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';

// Function to send emails
function sendPasswordResetEmail($email, $token) {
    $mail = new PHPMailer(true);
    try {
        // Server settings
        //$mail->SMTPDebug = SMTP::DEBUG_SERVER; // Enable verbose debug output
        $mail->isSMTP();                                            // Send using SMTP
        $mail->Host       = 'smtp.example.com';                     // Set the SMTP server
        $mail->SMTPAuth   = true;                                   // Enable SMTP authentication
        $mail->Username   = 'your_email@example.com';               // SMTP username
        $mail->Password   = 'your_email_password';                  // SMTP password
        $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;         // Enable TLS encryption
        $mail->Port       = 587;                                    // TCP port to connect to

        // Recipients
        $mail->setFrom('no-reply@yourdomain.com', 'Password Reset');
        $mail->addAddress($email);     // Add a recipient

        // Content
        $mail->isHTML(true);                                  // Set email format to HTML
        $mail->Subject = 'Password Reset Request';
        $resetLink = "http://localhost/password-reset-system/reset_password.php?token=$token";
        $mail->Body    = "<p>You requested a password reset. Click the link below to reset your password:</p>
                          <a href='$resetLink'>$resetLink</a>
                          <p>If you did not request this, please ignore this email.</p>";
        $mail->AltBody = "You requested a password reset. Visit the following link to reset your password: $resetLink";

        $mail->send();
        return true;
    } catch (Exception $e) {
        // Log error or handle accordingly
        return false;
    }
}
?>

Note: Replace 'smtp.example.com', 'your_email@example.com', and 'your_email_password' with your actual SMTP server details and credentials. For Gmail SMTP, use smtp.gmail.com with appropriate port and credentials. If using Gmail, ensure that “Less secure app access” is enabled or use App Passwords.

6. Creating Reusable Templates

a. Header (templates/header.php)
<?php
// templates/header.php
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Password Reset System</title>
    <link rel="stylesheet" href="/password-reset-system/assets/css/styles.css">
</head>
<body>
    <header>
        <h1>Password Reset System</h1>
        <nav>
            <a href="/password-reset-system/register.php">Register</a>
            <a href="/password-reset-system/login.php">Login</a>
            <a href="/password-reset-system/forgot_password.php">Forgot Password</a>
        </nav>
    </header>
    <main>
b. Footer (templates/footer.php)
<?php
// templates/footer.php
?>
    </main>
    <footer>
        <p>&copy; <?php echo date("Y"); ?> Your Company Name</p>
    </footer>
    <script src="/password-reset-system/assets/js/scripts.js"></script>
</body>
</html>

7. Styling the Application (assets/css/styles.css)

Add basic styles to enhance the appearance.

/* assets/css/styles.css */

body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
}

header, footer {
    background-color: #0066cc;
    color: #fff;
    padding: 10px 20px;
}

header h1, footer p {
    margin: 0;
}

nav a {
    color: #fff;
    margin-right: 15px;
    text-decoration: none;
}

main {
    padding: 20px;
}

form {
    max-width: 500px;
    margin: auto;
}

.form-group {
    margin-bottom: 15px;
}

label {
    display: block;
    margin-bottom: 5px;
}

input[type="text"],
input[type="email"],
input[type="password"] {
    width: 100%;
    padding: 8px;
    box-sizing: border-box;
}

button {
    padding: 10px 15px;
    background-color: #28a745;
    color: #fff;
    border: none;
    cursor: pointer;
}

button:hover {
    background-color: #218838;
}

.error {
    background-color: #f8d7da;
    color: #842029;
    padding: 15px;
    margin-bottom: 20px;
    border: 1px solid #f5c2c7;
    border-radius: 4px;
}

.success {
    background-color: #d1e7dd;
    color: #0f5132;
    padding: 15px;
    margin-bottom: 20px;
    border: 1px solid #badbcc;
    border-radius: 4px;
}

8. User Registration (register.php)

This page allows new users to create an account.

<?php
// register.php

require 'config/db.php';
require 'templates/header.php';

$errors = [];
$success = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitize and validate inputs
    $username = trim($_POST['username']);
    $email    = trim($_POST['email']);
    $password = trim($_POST['password']);
    $confirm_password = trim($_POST['confirm_password']);

    if (empty($username)) {
        $errors[] = 'Username is required.';
    }

    if (empty($email)) {
        $errors[] = 'Email is required.';
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Invalid email format.';
    }

    if (empty($password)) {
        $errors[] = 'Password is required.';
    } elseif (strlen($password) < 6) {
        $errors[] = 'Password must be at least 6 characters.';
    }

    if ($password !== $confirm_password) {
        $errors[] = 'Passwords do not match.';
    }

    // Check if username or email already exists
    if (empty($errors)) {
        $stmt = $pdo->prepare('SELECT id FROM users WHERE username = ? OR email = ?');
        $stmt->execute([$username, $email]);
        if ($stmt->fetch()) {
            $errors[] = 'Username or email already exists.';
        }
    }

    if (empty($errors)) {
        // Hash the password
        $hashed_password = password_hash($password, PASSWORD_DEFAULT);

        // Insert into database
        $stmt = $pdo->prepare('INSERT INTO users (username, email, password) VALUES (?, ?, ?)');
        if ($stmt->execute([$username, $email, $hashed_password])) {
            $success = 'Registration successful. You can now <a href="login.php">login</a>.';
            // Clear form fields
            $username = $email = $password = $confirm_password = '';
        } else {
            $errors[] = 'There was an error registering. Please try again.';
        }
    }
}
?>

<h2>Register</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo $success; ?></p>
    </div>
<?php endif; ?>

<form action="register.php" method="POST">
    <div class="form-group">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" value="<?php echo htmlspecialchars($username ?? ''); ?>" required>
    </div>

    <div class="form-group">
        <label for="email">Email:</label>
        <input type="email" id="email" name="email" value="<?php echo htmlspecialchars($email ?? ''); ?>" required>
    </div>

    <div class="form-group">
        <label for="password">Password (min 6 characters):</label>
        <input type="password" id="password" name="password" required>
    </div>

    <div class="form-group">
        <label for="confirm_password">Confirm Password:</label>
        <input type="password" id="confirm_password" name="confirm_password" required>
    </div>

    <button type="submit">Register</button>
</form>

<?php
require 'templates/footer.php';
?>

9. User Login (login.php)

This page allows existing users to log in.

<?php
// login.php

require 'config/db.php';
require 'templates/header.php';

$errors = [];
$success = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitize and validate inputs
    $username = trim($_POST['username']);
    $password = trim($_POST['password']);

    if (empty($username)) {
        $errors[] = 'Username is required.';
    }

    if (empty($password)) {
        $errors[] = 'Password is required.';
    }

    if (empty($errors)) {
        // Fetch user from database
        $stmt = $pdo->prepare('SELECT id, password FROM users WHERE username = ?');
        $stmt->execute([$username]);
        $user = $stmt->fetch();

        if ($user && password_verify($password, $user['password'])) {
            // Password is correct, start a session
            $_SESSION['user_id'] = $user['id'];
            $_SESSION['username'] = $username;
            // Redirect to dashboard or home page
            header('Location: forgot_password.php'); // Redirect to a protected page
            exit;
        } else {
            $errors[] = 'Invalid username or password.';
        }
    }
}
?>

<h2>Login</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo htmlspecialchars($success); ?></p>
    </div>
<?php endif; ?>

<form action="login.php" method="POST">
    <div class="form-group">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" value="<?php echo htmlspecialchars($username ?? ''); ?>" required>
    </div>

    <div class="form-group">
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required>
    </div>

    <button type="submit">Login</button>
</form>

<?php
require 'templates/footer.php';
?>

Note: Replace header('Location: forgot_password.php'); with an appropriate protected page, such as a dashboard.

10. Forgot Password (forgot_password.php)

This page allows users to request a password reset by entering their registered email.

<?php
// forgot_password.php

require 'config/db.php';
require 'send_email.php'; // Include email sending function
require 'templates/header.php';

$errors = [];
$success = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitize and validate inputs
    $email = trim($_POST['email']);

    if (empty($email)) {
        $errors[] = 'Email is required.';
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Invalid email format.';
    }

    if (empty($errors)) {
        // Check if email exists in database
        $stmt = $pdo->prepare('SELECT id FROM users WHERE email = ?');
        $stmt->execute([$email]);
        $user = $stmt->fetch();

        if ($user) {
            // Generate a unique token
            $token = bin2hex(random_bytes(50));
            $tokenExpiry = date("Y-m-d H:i:s", strtotime('+1 hour'));

            // Store token and expiry in database
            $stmt = $pdo->prepare('UPDATE users SET reset_token = ?, token_expiry = ? WHERE id = ?');
            if ($stmt->execute([$token, $tokenExpiry, $user['id']])) {
                // Send password reset email
                if (sendPasswordResetEmail($email, $token)) {
                    $success = 'A password reset link has been sent to your email.';
                } else {
                    $errors[] = 'Failed to send password reset email. Please try again later.';
                }
            } else {
                $errors[] = 'Failed to generate password reset token. Please try again.';
            }
        } else {
            $errors[] = 'No account found with that email address.';
        }
    }
}
?>

<h2>Forgot Password</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo htmlspecialchars($success); ?></p>
    </div>
<?php endif; ?>

<form action="forgot_password.php" method="POST">
    <div class="form-group">
        <label for="email">Enter your registered email address:</label>
        <input type="email" id="email" name="email" value="<?php echo htmlspecialchars($email ?? ''); ?>" required>
    </div>

    <button type="submit">Send Password Reset Link</button>
</form>

<?php
require 'templates/footer.php';
?>

11. Reset Password (reset_password.php)

This page allows users to reset their password using the token sent to their email.

<?php
// reset_password.php

require 'config/db.php';
require 'templates/header.php';

$errors = [];
$success = '';
$showForm = false;

// Get token from URL
$token = isset($_GET['token']) ? $_GET['token'] : '';

if ($token) {
    // Check if token exists and is not expired
    $stmt = $pdo->prepare('SELECT id, token_expiry FROM users WHERE reset_token = ?');
    $stmt->execute([$token]);
    $user = $stmt->fetch();

    if ($user) {
        $currentDate = date("Y-m-d H:i:s");
        if ($currentDate > $user['token_expiry']) {
            $errors[] = 'This password reset link has expired.';
        } else {
            $showForm = true;
            // Handle form submission
            if ($_SERVER['REQUEST_METHOD'] === 'POST') {
                $password = trim($_POST['password']);
                $confirm_password = trim($_POST['confirm_password']);

                if (empty($password)) {
                    $errors[] = 'Password is required.';
                } elseif (strlen($password) < 6) {
                    $errors[] = 'Password must be at least 6 characters.';
                }

                if ($password !== $confirm_password) {
                    $errors[] = 'Passwords do not match.';
                }

                if (empty($errors)) {
                    // Hash the new password
                    $hashed_password = password_hash($password, PASSWORD_DEFAULT);

                    // Update password and remove reset token
                    $stmt = $pdo->prepare('UPDATE users SET password = ?, reset_token = NULL, token_expiry = NULL WHERE id = ?');
                    if ($stmt->execute([$hashed_password, $user['id']])) {
                        $success = 'Your password has been reset successfully. You can now <a href="login.php">login</a>.';
                        $showForm = false;
                    } else {
                        $errors[] = 'There was an error resetting your password. Please try again.';
                    }
                }
            }
        }
    } else {
        $errors[] = 'Invalid password reset token.';
    }
} else {
    $errors[] = 'No password reset token provided.';
}
?>

<h2>Reset Password</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo $success; ?></p>
    </div>
<?php endif; ?>

<?php if ($showForm): ?>
    <form action="reset_password.php?token=<?php echo htmlspecialchars($token); ?>" method="POST">
        <div class="form-group">
            <label for="password">New Password (min 6 characters):</label>
            <input type="password" id="password" name="password" required>
        </div>

        <div class="form-group">
            <label for="confirm_password">Confirm New Password:</label>
            <input type="password" id="confirm_password" name="confirm_password" required>
        </div>

        <button type="submit">Reset Password</button>
    </form>
<?php endif; ?>

<?php
require 'templates/footer.php';
?>

12. Logging Out Users (logout.php)

This script logs users out by destroying their session.

<?php
// logout.php

session_start();
session_unset();
session_destroy();

// Redirect to login page
header('Location: login.php');
exit;
?>

13. Testing the Application

  1. Register a New User:
    • Navigate to http://localhost/password-reset-system/register.php.
    • Fill in the registration form and submit.
    • Ensure that the registration is successful.
  2. Login with the Registered User:
    • Navigate to http://localhost/password-reset-system/login.php.
    • Enter your credentials and log in.
    • Ensure that you are redirected appropriately.
  3. Request a Password Reset:
    • Navigate to http://localhost/password-reset-system/forgot_password.php.
    • Enter your registered email address and submit.
    • Check your email for the password reset link.
  4. Reset the Password:
    • Click on the password reset link received in your email.
    • Enter a new password and confirm it.
    • Submit the form and ensure that the password is reset successfully.
  5. Login with the New Password:
    • Navigate to http://localhost/password-reset-system/login.php.
    • Enter your username and the new password.
    • Ensure that you can log in successfully.
  6. Handle Invalid or Expired Tokens:
    • Try accessing reset_password.php with an invalid or expired token.
    • Ensure that appropriate error messages are displayed.

14. Deployment Considerations

When deploying your Password Reset System to a live server, consider the following:

  • Secure SMTP Credentials:
    • Store SMTP credentials securely using environment variables or secure configuration files outside the web root.
  • Use HTTPS:
    • Implement HTTPS to ensure secure data transmission, especially for login and password reset functionalities.
  • Email Deliverability:
    • Use reputable email services to ensure that password reset emails are delivered reliably and not marked as spam.
  • Token Security:
    • Ensure that reset tokens are sufficiently random and have a limited lifespan to prevent misuse.
  • Input Validation and Sanitization:
    • Thoroughly validate and sanitize all user inputs to prevent SQL injection and XSS attacks.
  • Error Handling:
    • Avoid displaying sensitive error messages to users. Instead, log errors securely on the server.
  • Rate Limiting:
    • Implement rate limiting on password reset requests to prevent abuse and potential attacks.
  • Regular Backups:
    • Schedule regular backups of your database to prevent data loss.
  • Session Security:
    • Implement secure session handling practices, such as regenerating session IDs upon login.

15. Enhancements and Best Practices

  • Email Templates:
    • Design user-friendly and branded email templates for password reset emails.
  • User Notifications:
    • Notify users when their password is changed successfully.
  • Audit Logs:
    • Implement logging of password reset requests for security auditing.
  • Account Lockout Mechanism:
    • Lock accounts temporarily after multiple failed login attempts to prevent brute-force attacks.
  • Password Strength Validation:
    • Enforce strong password policies to enhance account security.
  • CAPTCHA Integration:
    • Integrate CAPTCHA in registration and password reset forms to prevent automated abuse.
  • Two-Factor Authentication (2FA):
    • Enhance security by implementing 2FA alongside password-based authentication.
Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

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