🔐 Build a Secure Password Reset System using Node.js, Express, MongoDB & OTP (Step-by-Step Full Guide)

Learn how to build a secure password reset system using Node.js, Express, MongoDB and OTP verification step by step with full-stack implementation.

A password reset system is one of the most critical components in any authentication-based application. Whether it is an e-commerce platform, SaaS product, or banking system, users will eventually forget their password.

In this full-stack password reset system tutorial, you will learn how to build a secure, production-style password recovery system using Node.js, Express, MongoDB, bcrypt, and email-based OTP verification.

This tutorial is designed for beginners but written in a professional way so that you understand both how it works and why it works.


📌 1. What You Will Build

We will build a complete password reset flow that includes:

  • User requests password reset using email
  • System generates a secure OTP (One-Time Password)
  • OTP is sent to user's email
  • User verifies OTP
  • User sets a new password
  • System securely updates password in database

This is the same architecture used in real-world applications like Google, Amazon, and banking apps.

Figure 1: Full-Stack Authentication Architecture (Frontend → Backend → MongoDB with JWT Flow)


🧠 2. Why Password Reset System is Important

A password reset system is not just a feature — it is a security requirement.

  • Users lose access permanently
  • Support load increases
  • Security risks increase due to unsafe recovery methods

A properly designed reset system ensures:

  • Only real email owner can reset password
  • OTP expires automatically
  • Passwords are never exposed in plain text

⚙️ 3. Project Setup (Node.js Initialization)

npm init -y
npm install express mongoose bcrypt nodemailer dotenv cors

These packages are used for:

  • Express: API server
  • Mongoose: MongoDB connection
  • Bcrypt: Password hashing
  • Nodemailer: Sending emails
  • Dotenv: Environment variables

🗄️ 4. Creating Express Server


const express = require("express");
const app = express();

app.use(express.json());

app.listen(3000, () => {
    console.log("Server running on port 3000");
});

This is the base of our backend system. Express will handle all API requests.


🧩 5. Connecting MongoDB Database


const mongoose = require("mongoose");

mongoose.connect("mongodb://localhost:27017/authSystem", {
    useNewUrlParser: true,
    useUnifiedTopology: true
}).then(() => {
    console.log("MongoDB connected");
});

MongoDB will store user data, OTP codes, and password information.


👤 6. User Schema Design


const mongoose = require("mongoose");

const userSchema = new mongoose.Schema({
    email: String,
    password: String,
    otp: String,
    otpExpiry: Date
});

module.exports = mongoose.model("User", userSchema);

This schema supports OTP-based password recovery system.

Figure 2: MongoDB User Schema Structure and API Request-Response Flow


🔐 7. Password Hashing with bcrypt


const bcrypt = require("bcrypt");

async function hashPassword(password) {
    const salt = await bcrypt.genSalt(10);
    return await bcrypt.hash(password, salt);
}

Even if database is leaked, hashed passwords cannot be easily reversed.


📧 8. Email OTP System Setup


const nodemailer = require("nodemailer");

const transporter = nodemailer.createTransport({
    service: "gmail",
    auth: {
        user: "your_email@gmail.com",
        pass: "your_app_password"
    }
});

This system sends OTP to user email securely.


🔑 9. Generate Password Reset OTP


app.post("/request-reset", async (req, res) => {
    const { email } = req.body;

    const user = await User.findOne({ email });
    if (!user) return res.send("User not found");

    const otp = Math.floor(100000 + Math.random() * 900000);

    user.otp = otp;
    user.otpExpiry = Date.now() + 10 * 60 * 1000;

    await user.save();

    await transporter.sendMail({
        from: "CodeBasedLearning",
        to: email,
        subject: "Password Reset OTP",
        text: `Your OTP is: ${otp}`
    });

    res.send("OTP sent to email");
});

🔐 10. Verify OTP & Reset Password


app.post("/reset-password", async (req, res) => {
    const { email, otp, newPassword } = req.body;

    const user = await User.findOne({ email });

    if (!user) return res.send("Invalid user");

    if (user.otp !== otp || Date.now() > user.otpExpiry) {
        return res.send("Invalid or expired OTP");
    }

    const hashedPassword = await bcrypt.hash(newPassword, 10);

    user.password = hashedPassword;
    user.otp = null;

    await user.save();

    res.send("Password reset successful");
});

🔐 11. Middleware for Protected Routes

JWT middleware is used to protect routes by verifying the token before allowing access.

Figure 3: JWT Authentication Workflow (Login → Token Generation → Verification → Protected Route Access)

This ensures that only authenticated users can access secure endpoints.


🧠 12. Frontend Password Reset Form



⚡ 13. Frontend JavaScript (Fetch API)


async function requestOTP() {
    let email = document.getElementById("email").value;

    await fetch("/request-reset", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email })
    });
}

async function resetPassword() {
    let data = {
        email: document.getElementById("email").value,
        otp: document.getElementById("otp").value,
        newPassword: document.getElementById("newPassword").value
    };

    await fetch("/reset-password", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(data)
    });
}

⏳ 14. OTP Expiry System

  • Prevents OTP reuse
  • Blocks brute-force attacks
  • Improves system security

🔒 15. Security Best Practices

  • Never store plain passwords
  • Always hash passwords using bcrypt
  • Use environment variables for secrets
  • Limit OTP attempts
  • Add rate limiting for API endpoints

🌍 16. Real-World Applications

  • E-commerce platforms
  • SaaS applications
  • Banking systems
  • Educational portals

🚀 17. Deployment Tips

  • Deploy backend on Render or AWS
  • Use MongoDB Atlas
  • Configure environment variables
  • Use HTTPS for secure communication

❓ FAQs

1. Is OTP secure for password reset?

Yes, if combined with expiry time and hashing.

2. How long should OTP be valid?

Usually 5–10 minutes is recommended.

3. Can attackers bypass OTP?

Not easily if rate limiting and expiry are used.

4. Is email OTP better than SMS?

Email is cheaper and widely used for web apps.

5. Can I use this in production?

Yes, with proper security improvements like rate limiting.


🎯 Conclusion

You now understand how to build a secure password reset system using Node.js, Express, MongoDB, and OTP verification.

This system is a fundamental part of modern authentication workflows and improves user experience and security.

Prasun Barua is a graduate engineer in Electrical and Electronic Engineering with a passion for simplifying complex technical concepts for learners and professionals alike. He has authored numerous highly regarded books covering a wide range of electrical, electronic, and renewable energy topics. Some of his notable works include Electronics Transistor Basics, Fundamentals of Electrical Substations, Digital Electronics – Logic Gates, Boolean Algebra in Digital Electronics, Solid State Physics Fundamentals, MOSFET Basics, Semiconductor Device Fabrication Process, DC Circuit Basics, Diode Basics, Fundamentals of Battery, VLSI Design Basics, How to Design and Size Solar PV Systems, Switchgear and Protection, Electromagnetism Basics, Semiconductor Fundamentals, and Green Planet. His books are designed to provide clear, concise, and practical knowledge, making them valuable resources for students, engineers, and technology enthusiasts worldwide. All of these titles are available on Amazon…