Membuat Fitur Reset Password melalui Email dengan PHPMailer di PHP MySQL

By | 14 October 2025

Fitur Lupa Password / Reset Password adalah bagian penting dari sistem login modern. Namun jika dibuat sembarangan, ini bisa jadi pintu masuk bagi peretas. Untuk itu, kita akan membuat sistem reset password melalui email aman yang:

  • Mengirim link verifikasi lewat email (via PHPMailer),

  • Menggunakan token unik yang dienkripsi,

  • Memiliki masa berlaku terbatas (misalnya 15 menit),

  • Menghapus token otomatis setelah digunakan.

Struktur Tabel Database

Tambahkan tabel baru untuk menyimpan token reset password.

CREATE TABLE password_resets (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  selector CHAR(12) NOT NULL,
  hashed_token CHAR(64) NOT NULL,
  expires DATETIME NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Token dipecah menjadi selector (identifikasi cepat) dan token (rahasia, di-hash di database).

Form Lupa Password

File: forgot_password.php

<?php
include 'config.php';
require 'vendor/autoload.php'; // PHPMailer

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

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $email = trim($_POST['email']);
    $stmt = $conn->prepare("SELECT id, username FROM users WHERE email=?");
    $stmt->bind_param("s", $email);
    $stmt->execute();
    $result = $stmt->get_result();

    if ($result->num_rows) {
        $user = $result->fetch_assoc();
        $user_id = $user['id'];

        // Hapus token lama (jika ada)
        $conn->query("DELETE FROM password_resets WHERE user_id='$user_id'");

        // Buat token baru
        $selector = bin2hex(random_bytes(6));
        $token = random_bytes(33);
        $hashedToken = hash('sha256', $token);
        $expires = date('Y-m-d H:i:s', time() + 900); // 15 menit

        $stmt = $conn->prepare("INSERT INTO password_resets (user_id, selector, hashed_token, expires) VALUES (?, ?, ?, ?)");
        $stmt->bind_param("isss", $user_id, $selector, $hashedToken, $expires);
        $stmt->execute();

        $url = "https://yourdomain.com/reset_password.php?selector=$selector&token=" . bin2hex($token);

        // Kirim email dengan PHPMailer
        $mail = new PHPMailer(true);
        try {
            $mail->isSMTP();
            $mail->Host = 'smtp.yourhost.com';
            $mail->SMTPAuth = true;
            $mail->Username = 'youremail@domain.com';
            $mail->Password = 'yourpassword';
            $mail->SMTPSecure = 'tls';
            $mail->Port = 587;

            $mail->setFrom('no-reply@domain.com', 'Support System');
            $mail->addAddress($email, $user['username']);
            $mail->isHTML(true);
            $mail->Subject = 'Reset Password Anda';
            $mail->Body = "
                <h3>Hai {$user['username']},</h3>
                <p>Kami menerima permintaan untuk reset password akun Anda.</p>
                <p>Klik link berikut untuk mengganti password Anda (berlaku 15 menit):</p>
                <p><a href='$url'>$url</a></p>
                <br>
                <p>Abaikan jika Anda tidak meminta reset password ini.</p>
            ";
            $mail->send();

            echo "<p style='color:green'>Link reset password telah dikirim ke email Anda.</p>";
        } catch (Exception $e) {
            echo "<p style='color:red'>Gagal mengirim email. Error: {$mail->ErrorInfo}</p>";
        }
    } else {
        echo "<p style='color:red'>Email tidak ditemukan.</p>";
    }
}
?>

<form method="post">
    <label>Email:</label>
    <input type="email" name="email" required>
    <button type="submit">Kirim Link Reset</button>
</form>

Halaman Reset Password

File: reset_password.php

<?php
include 'config.php';

if (isset($_GET['selector'], $_GET['token'])) {
    $selector = $_GET['selector'];
    $token = hex2bin($_GET['token']);
} else {
    die('Token tidak valid');
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $password = $_POST['password'];
    $password2 = $_POST['password2'];

    if ($password !== $password2) {
        echo "<p style='color:red'>Password tidak cocok.</p>";
    } else {
        // Validasi token
        $stmt = $conn->prepare("SELECT * FROM password_resets WHERE selector=? AND expires > NOW()");
        $stmt->bind_param("s", $selector);
        $stmt->execute();
        $result = $stmt->get_result();
        $row = $result->fetch_assoc();

        if ($row && hash_equals($row['hashed_token'], hash('sha256', $token))) {
            $user_id = $row['user_id'];

            // Update password user
            $hashedPassword = password_hash($password, PASSWORD_BCRYPT);
            $stmt = $conn->prepare("UPDATE users SET password=? WHERE id=?");
            $stmt->bind_param("si", $hashedPassword, $user_id);
            $stmt->execute();

            // Hapus token setelah digunakan
            $conn->query("DELETE FROM password_resets WHERE user_id='$user_id'");

            echo "<p style='color:green'>Password berhasil diubah. Silakan login.</p>";
        } else {
            echo "<p style='color:red'>Token tidak valid atau sudah kedaluwarsa.</p>";
        }
    }
}
?>

<form method="post">
    <label>Password Baru:</label>
    <input type="password" name="password" required><br>
    <label>Ulangi Password:</label>
    <input type="password" name="password2" required><br>
    <button type="submit">Ubah Password</button>
</form>

Pembersihan Token Kadaluarsa (Opsional)

Untuk menjaga keamanan dan efisiensi, hapus token reset password yang sudah lewat masa aktifnya.

Contoh cron job PHP sederhana (clean_expired_tokens.php):

<?php
include 'config.php';
$conn->query("DELETE FROM password_resets WHERE expires < NOW()");
?>

Jalankan otomatis tiap jam via cron:

0 * * * * /usr/bin/php /path/to/clean_expired_tokens.php

Integrasi dengan Sistem Login Sebelumnya

Fitur ini kompatibel dengan sistem login multi-device sebelumnya karena:

  • Password disimpan dengan hash aman (password_hash),

  • Token disimpan secara terpisah dari sistem login,

  • Reset password tidak menghapus sesi Remember Me, tapi bisa ditambahkan jika ingin auto logout semua device.

Contoh jika ingin logout semua device setelah reset password:

<?php
$conn->query("DELETE FROM remember_tokens WHERE user_id='$user_id'");
?>

Tambahkan baris ini setelah update password di reset_password.php.

Tips Keamanan Tambahan

✅ Gunakan HTTPS agar token tidak bocor di URL.
✅ Gunakan PHPMailer dengan SMTP authentication, jangan mail() bawaan.
✅ Batasi jumlah permintaan reset (misalnya 3 kali per jam).
✅ Tambahkan captcha untuk mencegah brute force form email.
✅ Hindari menampilkan pesan “email tidak ditemukan” secara eksplisit di versi produksi (bisa diganti dengan pesan umum).

Kesimpulan

Dengan sistem ini, kamu sudah punya fitur reset password aman via email yang terintegrasi dengan sistem login multi-device PHP.
Pengguna dapat dengan mudah mengganti password tanpa menurunkan keamanan akun.

Fitur utama yang kamu dapat:

  • Token unik & terenkripsi

  • Link kadaluwarsa otomatis

  • Integrasi dengan PHPMailer

  • Kompatibel dengan sistem login multi-perangkat

Category: PHP