🔐 Build a Secure Password Reset System using Node.js, Express, MongoDB & OTP (Step-by-Step Full Guide)
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.
.png)
Join the conversation