


How to forgot password using Mern stack with cloudinary cloud storage?
Introduction
In this blog post, we'll walk through the process of setting up a "Forgot Password" feature using the MERN stack (MongoDB, Express.js, React, Node.js) with Next.js. We'll cover everything from setting up the backend with Node.js and Express to building the frontend with Next.js. By the end, you'll have a comprehensive understanding of how to implement a secure and user-friendly password reset functionality.
Prerequisites
Basic knowledge of JavaScript and React.
Understanding of Node.js and Express.js.
Familiarity with MongoDB and Mongoose.
Experience with Next.js.
Step 1: Setting Up the Backend
1.1. Initialize the Node.js Project
Create a new directory for your project and initialize a Node.js project:
mkdir mern-password-reset cd mern-password-reset npm init -y
1.2. Install Dependencies
Install the necessary dependencies:
npm install express mongoose bcryptjs jsonwebtoken nodemailer dotenv
express: Web framework for Node.js.
mongoose: MongoDB object modeling tool.
bcryptjs: Library to hash passwords.
jsonwebtoken: For generating and verifying JWT tokens.
nodemailer: For sending emails.
dotenv: To manage environment variables.
1.3. Set Up Environment Variables
Create a .env file in the root of your project to store your environment variables:
PORT=5000 MONGO_URI=your_mongodb_connection_string JWT_SECRET=your_jwt_secret EMAIL_USER=your_email@example.com EMAIL_PASS=your_email_password
1.4. Set Up the Express Server
Create a server.js file and set up the basic Express server:
const express = require('express'); const mongoose = require('mongoose'); const dotenv = require('dotenv'); const app = express(); dotenv.config(); const port = process.env.PORT || 5001; //middlewares app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()); mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, }); app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); });
1.5. Create User Model
Create a models directory and add a User.js file for the User model:
const mongoose = require("mongoose"); const userSchema = new mongoose.Schema( { firstName: { type: String, require: true }, lastName: { type: String, require: true }, email: { type: String, require: true, unique: true }, password: { type: String, require: true }, mobileNo: { type: String, require: true }, image: { type: String, default: "" }, dob: { type: String, require: true }, }, { timestamps: true } ); const User = mongoose.model("user", userSchema); module.exports = User;
1.6. Create Auth Routes
Create a routes directory and add an auth.js file for authentication routes:
const express = require("express"); const { register, login, requestPasswordResetController, resetPasswordController, } = require("../controllers/user"); const router = express.Router(); router.post("/login", login); router.post("/requestPasswordReset", requestPasswordResetController); router.post("/resetPassword", resetPasswordController); module.exports = router;
1.7. Add Cloudinary for Profile Picture Upload
Install the necessary dependencies for Cloudinary:
npm install cloudinary multer multer-storage-cloudinary
- cloudinary: Cloud-based image and video management.
- multer: Middleware for handling multipart/form-data.
- multer-storage-cloudinary: Integration between multer and cloudinary.
1.8. Configure Cloudinary
Create a config directory and add a cloudinaryConfig.js file:
const cloudinary = require('cloudinary').v2; const { CloudinaryStorage } = require('multer-storage-cloudinary'); cloudinary.config({ cloud_name: process.env.CLOUDINARY_CLOUD_NAME, api_key: process.env.CLOUDINARY_API_KEY, api_secret: process.env.CLOUDINARY_API_SECRET, }); const storage = new CloudinaryStorage({ cloudinary, params: { folder: 'profile_pics', allowedFormats: ['jpg', 'png'], }, }); module.exports = { cloudinary, storage };
Update your .env file to include Cloudinary configuration:
CLOUDINARY_CLOUD_NAME=your_cloud_name CLOUDINARY_API_KEY=your_api_key CLOUDINARY_API_SECRET=your_api_secret
1.9. Define Token Schema
Create a models directory and add a tokenSchema.js file:
const mongoose = require("mongoose"); const tokenSchema = new mongoose.Schema({ userId: { type: mongoose.Schema.Types.ObjectId, require: true, ref: "User" }, token: { type: String, require: true }, createdAt: { type: Date, default: Date.now, expires: 3600 }, }); const Token = mongoose.model("Token", tokenSchema); module.exports = Token;
1.10. Implement Auth Service
Create a services directory and add an authService.js file:
const Token = require("../models/tokenSchema"); const User = require("../models/userSchema"); const crypto = require("crypto"); const bcrypt = require("bcryptjs"); const sendEmail = require("../config/sendEmail"); const requestPasswordReset = async (email) => { try { const user = await User.findOne({ email }); if (!user) { throw new Error("User doesn't exist"); } const name = `${user.firstName} ${user.lastName}`; let token = await Token.findOne({ userId: user._id }); if (token) await token.deleteOne(); let resetToken = crypto.randomBytes(32).toString("hex"); const salt = await bcrypt.genSalt(10); const hash = await bcrypt.hash(resetToken, salt); const newToken = await Token.create({ userId: user._id, token: hash, createdAt: Date.now(), }); const link = `${process.env.CLIENT_URL}/forgot-password/reset?token=${resetToken}&id=${user._id}`; const htmlContent = ` <h1>Hi, ${name}</h1> <p>You requested to reset your password.</p> <p>Please, click the link below to reset your password.</p> <a href="${link}">Reset Password</a>`; sendEmail(`${email}`, "Request to reset password", htmlContent); return link; } catch (error) { console.log(error.message); } }; const resetPassword = async (userId, token, password) => { try { let passwordResetToken = await Token.findOne({ userId }); if (!passwordResetToken) { throw new Error("Invalid or expired password reset token"); } const isValid = await bcrypt.compare(token, passwordResetToken.token); if (!isValid) { throw new Error("Invalid or expired password reset token"); } const salt = await bcrypt.genSalt(10); const hash = await bcrypt.hash(password, salt); await User.updateOne( { _id: userId }, { $set: { password: hash } }, { new: true } ); const user = await User.findById({ _id: userId }); const name = `${user.firstName} ${user.lastName}`; const htmlContent = `<h1>Hi, ${name}</h1> <p>Your password reset successfully.</p>`; sendEmail(user.email, "Password Reset Successfully", htmlContent); await passwordResetToken.deleteOne(); return "Password Reset Successfully"; } catch (error) { console.log(error.message); } }; module.exports = { requestPasswordReset, resetPassword };
requestPasswordReset(email): Generates a password reset token, saves it in the database, and sends an email to the user with the reset link.
resetPassword(userId, token, password): Verifies the token, hashes the new password, updates the user's password in the database, and sends a confirmation email.
1.11. Implementing User Controller
Create a controllers directory and add a userController.js file:
const User = require("../models/userSchema"); const bcrypt = require("bcryptjs"); const generateToken = require("../config/generateToken"); const { requestPasswordReset, resetPassword } = require("../services/authService"); const Token = require("../models/tokenSchema"); const cloudinary = require("cloudinary").v2; const register = async (req, res) => { try { const { firstName, lastName, email, password, confirmPassword, mobileNo, dob, } = req.body; const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash(password, salt); const image = req.file.path; const imagePublicId = req.file.filename; const emailExist = await User.findOne({ email }); if (emailExist) { await cloudinary.uploader.destroy(imagePublicId); res.status(409).send({ message: "User with this email already exists" }); } if (password != confirmPassword) { await cloudinary.uploader.destroy(imagePublicId); res .status(400) .send({ message: "Password and Confirm password did not match" }); } if (!emailExist && password === confirmPassword) { const newUser = await User.create({ firstName: firstName, lastName: lastName, email: email, password: hashedPassword, mobileNo: mobileNo, dob: dob, image: image, }); res.send({ firstName: firstName, lastName: lastName, email: email, password: hashedPassword, mobileNo: mobileNo, dob: dob, token: generateToken(newUser._id), }); } } catch (error) { if (req.file && req.file.filename) { await cloudinary.uploader.destroy(req.file.filename); } console.log(error); } }; const login = async (req, res) => { try { const { email, password } = req.body; const user = await User.findOne({ email }); if (!user) { res.status(404).send({ message: "Email id is wrong" }); } if (user) { const validPassword = await bcrypt.compare(password, user.password); if (!validPassword) { res.status(401).send({ message: "Password is wrong" }); } else { res.status(200).send({ user: user, token: generateToken(user._id), }); } } } catch (error) { console.log(error); } }; const requestPasswordResetController = async (req, res) => { try { const email = req.body.email; const user = await User.findOne({ email }); if (!user) { res.status(404).send({ message: "User not found" }); } const requestPasswordResetService = await requestPasswordReset(email); return res.status(200).send(requestPasswordResetService); } catch (error) { res.status(500).send({ message: error.message }); console.log(error.message); } }; const resetPasswordController = async (req, res) => { try { const userId = req.body.userId; const token = req.body.token; const password = req.body.password; let passwordResetToken = await Token.findOne({ userId }); const resetPasswordService = await resetPassword(userId, token, password); if (!passwordResetToken) { res.status(404).send({ message: "Token not found or expired" }); } return res.status(200).send(resetPasswordService); } catch (error) { console.log(error.message); return res.status(500).send({ message: error.message }); } }; module.exports = { register, login, requestPasswordResetController, resetPasswordController, };
- register(req, res) Handles the registration of a new user. Here's a breakdown of its functionality:
- Input: Expects data from the request body (req.body) containing user information such as firstName, lastName, email, password, confirmPassword, mobileNo, dob, and optionally image (for profile picture).
- Steps:
Hashes the password using bcrypt for security.
Checks if the email already exists in the database (User.findOne({ email })).
If the email exists, it responds with a 409 status (conflict).
If the passwords (password and confirmPassword) don't match, it responds with a 400 status (bad request).
If everything is valid, it creates a new User document in MongoDB using User.create() with hashed password and uploads the profile picture to Cloudinary.
Responds with a success message or user data along with a JWT token generated using generateToken(newUser._id).
login(req, res)
Handles user login authentication. Here's how it works:
Input: Expects email and password from the request body (req.body).
Steps:
Finds a user in the database by email (User.findOne({ email })).
If no user is found, responds with a 404 status (not found).
Compares the provided password with the stored hashed password using bcrypt.
If passwords match, generates a JWT token (generateToken(user._id)) and sends it along with the user data in the response.
If passwords don't match, responds with a 401 status (unauthorized).
requestPasswordResetController(req, res)
Initiates the password reset process for a user:
Input: Expects email from the request body (req.body).
Steps:
Finds a user in the database by email (User.findOne({ email })).
If no user is found, responds with a 404 status (not found).
Calls requestPasswordReset(email) from authService to initiate the password reset process.
Sends a response with the result of the password reset request.
- resetPasswordController(req, res) Handles resetting a user's password:
Input: Expects userId, token, and password from the request body (req.body).
Steps:
Finds a password reset token for the user (Token.findOne({ userId })).
If no token is found, responds with a 404 status (not found).
Verifies the provided token against the stored hashed token using bcrypt.
If the token is valid, hashes the new password using bcrypt and updates the user's password in the database (User.updateOne()).
Sends a password reset confirmation email using sendEmail() and deletes the used password reset token (passwordResetToken.deleteOne()).
Responds with a success message.
1.12. Send Email Utility
Create a config directory and add a sendEmail.js file:
const nodemailer = require("nodemailer"); const sendEmail = async (email, subject, text) => { try { const transporter = nodemailer.createTransport({ host: process.env.HOST, service: process.env.SERVICE, port: 465, secure: true, auth: { user: process.env.USER, pass: process.env.PASS, }, }); await transporter.sendMail({ from: "Your App Name", to: email, subject: subject, html: text, }); console.log("Email sent successfully"); } catch (error) { console.log(error, "Email not sent"); } }; module.exports = sendEmail;
- The sendEmail function integrates Nodemailer to handle email sending operations in your application. It's configured with SMTP settings using environment variables for security, sends HTML-formatted emails, and includes error handling to manage potential transmission issues effectively. This abstraction simplifies email-related tasks across your application, ensuring reliable communication with users.
1.13. Integrate Routes into the Server and cloudinary cloud image update
Update server.js to include the authentication routes:
const express = require("express"); const dotenv = require("dotenv"); const cors = require("cors"); const connectDB = require("./db/db"); const authRoutes = require("./routes/user"); const bodyParser = require("body-parser"); const multer = require("multer"); const { storage } = require("./config/storage"); const { register } = require("./controllers/user"); const employeeRoutes = require("./routes/employee"); const transactionRoutes = require("./routes/transaction"); const app = express(); dotenv.config(); const port = process.env.PORT || 5001; //middlewares app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()); const upload = multer({ storage }); //routes app.use("/api/auth", authRoutes); //routes with files app.post("/api/auth/register", upload.single("file"), register); mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, }); app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); });
Step 2: Setting Up the Frontend
2.1. Initialize the Next.js Project
Create a new Next.js project:
npx create-next-app@latest mern-password-reset-client cd mern-password-reset-client
2.2. Install Dependencies
Install the necessary dependencies:
npm install redux react-redux @reduxjs/toolkit react-toastify @heroicons/react @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome axios next-themes
2.3. Create Pages and Components
Create pages and components for registration, login, forgot password, and reset password.
app/register/page.js
import Register from "@/components/Auth/Register"; import React from "react"; const page = () => { return ( <div> <Register /> </div> ); }; export default page;
component/register.jsx
"use client"; import React, { useEffect, useState } from "react"; import styles from "./auth.module.css"; import Logo from "../misc/Logo/Logo"; import Link from "next/link"; import axios from "axios"; import { redirect } from "next/navigation"; import { Bounce, toast } from "react-toastify"; import { useTheme } from "next-themes"; import { useDispatch, useSelector } from "react-redux"; import { setRegister } from "@/redux/slice/authSlice"; const Register = () => { const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmpassword] = useState(""); const [mobileNo, setMobileNo] = useState(""); const [dob, setDob] = useState(""); const [file, setFile] = useState(""); const [empData, setEmpData] = useState(); const { theme } = useTheme(); const token = useSelector((state) => state.auth.token); const dispatch = useDispatch(); const mergeEmployeeDetails = async () => { try { const config = { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, }; const result = await axios.get( "http://localhost:5000/api/employee/employeeDetails", config ); setEmpData(result); } catch (error) { console.log(error); } }; const handleRegister = async () => { const formData = new FormData(); formData.append("firstName", firstName); formData.append("lastName", lastName); formData.append("email", email); formData.append("password", password); formData.append("confirmPassword", confirmPassword); formData.append("mobileNo", mobileNo); formData.append("dob", dob); formData.append("file", file); try { const config = { headers: { "Content-Type": "multipart/form-data", }, }; const result = await axios.post( "http://localhost:5000/api/auth/register", formData, config ); const regUser = result.data; console.log(regUser); if (regUser) { dispatch( setRegister({ token: regUser.token, }) ); } toast.success("Successfully Registered", { position: "top-right", autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "dark", transition: Bounce, }); mergeEmployeeDetails(); redirect("/login"); } catch (error) { toast.error(error.response.data.message, { position: "top-right", autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "dark", transition: Bounce, }); console.log(error.response.data); } }; useEffect(() => { if (token) { mergeEmployeeDetails(); } }, [token]); return ( <div className={styles.container}> <div className={styles.auth_container}> <div className={styles.auth}> <div className={styles.auth_headers}> <div className={styles.upper_part}> <div className={styles.upper_part_text}>Welcome to</div>{" "} <div> <Logo /> </div> </div> <div className={styles.lower_part}> Register yourself to N&N finance </div> </div> <div className={styles.auth_form}> <div className={styles.input_group}> <div className={styles.input}> <div className={styles.inputTwo}> <div> <div> <label htmlFor="">First Name</label> </div> <div> {" "} <input type="text" placeholder="Enter your first name" name="firstName" value={firstName} onChange={(e) => setFirstName(e.target.value)} /> </div> </div> <div> <div> <label htmlFor="">Last Name</label> </div> <div> {" "} <input type="text" placeholder="Enter your last name" name="lastName" value={lastName} onChange={(e) => setLastName(e.target.value)} /> </div> </div> </div> </div> <div className={styles.input}> <div> <label htmlFor="">Email</label> </div> <div> <input type="email" placeholder="Enter your email" name="email" value={email} onChange={(e) => setEmail(e.target.value)} /> </div> </div> <div className={styles.input}> <div> <label htmlFor="">Mobile No</label> </div> <div> <input type="text" placeholder="Enter your mobile number" name="mobileNo" value={mobileNo} onChange={(e) => setMobileNo(e.target.value)} /> </div> </div> <div className={styles.input}> <div> <label htmlFor="">Password</label> </div> <div> <input type="password" placeholder="Enter password" name="password" value={password} onChange={(e) => setPassword(e.target.value)} /> </div> </div> <div className={styles.input}> <div> {" "} <label htmlFor="">Confirm Password</label> </div> <div> <input type="password" placeholder="Confirm Password" name="confirmPassword" value={confirmPassword} onChange={(e) => setConfirmpassword(e.target.value)} /> </div> </div> <div className={styles.input}> <div> <label htmlFor="">Date of Birth</label> <input type="date" placeholder="Enter your DOB" name="dob" value={dob} onChange={(e) => setDob(e.target.value)} /> </div> </div> <div className={styles.input}> <div> <input type="file" placeholder="Enter your image" name="file" onChange={(e) => setFile(e.target.files[0])} /> </div> </div> <div className={styles.btn}> <button onClick={handleRegister}>Register</button> </div> </div> <div className={styles.bottom_part}> Already have account? <Link href="/">Login</Link> </div> </div> </div> </div> </div> ); }; export default Register;
app/login/page.js
"use client"; import Login from "@/components/Auth/Login"; import { setLogin } from "@/redux/slice/authSlice"; import { handleLogin } from "@/services/api"; import axios from "axios"; import { useTheme } from "next-themes"; import Image from "next/image"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useDispatch } from "react-redux"; import { Bounce, toast } from "react-toastify"; export default function Home() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const dispatch = useDispatch(); const router = useRouter(); const theme = useTheme(); return ( <> <Login email={email} password={password} setEmail={setEmail} setPassword={setPassword} handleLogin={() => handleLogin(email, password, dispatch, router, theme) } /> </> ); }
component/login.jsx
"use client"; import React from "react"; import styles from "./auth.module.css"; import Link from "next/link"; import Logo from "../misc/Logo/Logo"; const Login = ({ email, password, setEmail, setPassword, handleLogin }) => { return ( <div className={styles.auth_container_login}> <div className={styles.auth}> <div className={styles.auth_headers}> <div className={styles.upper_part}> <div className={styles.upper_part_text}>Welcome to</div>{" "} <div> <Logo /> </div> </div> <div className={styles.lower_part}> Logged In yourself to N&N finance </div> </div> <div className={styles.auth_form}> <div className={styles.input_group}> <div className={styles.input}> <div> <label htmlFor="">Email</label> </div> <div> <input type="email" placeholder="Enter your email" value={email} onChange={(e) => setEmail(e.target.value)} /> </div> </div> <div className={styles.input}> <div> <label htmlFor="">Password</label> </div> <div> <input type="password" placeholder="Enter password" value={password} onChange={(e) => setPassword(e.target.value)} /> </div> </div> <div className={styles.btn}> <button onClick={handleLogin}>Login</button> </div> </div> <div className={styles.forgot_password}> <Link href="/forgot-password">Forgot Password?</Link> </div> <div className={styles.bottom_part}> Already have account? <Link href="/register">Sign Up</Link> </div> </div> </div> </div> ); }; export default Login;
service/api.js
import axios from "axios"; import { Bounce, toast } from "react-toastify"; import { setLogin } from "@/redux/slice/authSlice"; export const handleLogin = async (email, password, dispatch, router, theme) => { try { const config = { headers: { "Content-Type": "application/json", }, }; const result = await axios.post( "http://localhost:5000/api/auth/login", { email, password }, config ); router.push("/dashboard"); const loggedIn = result.data; if (loggedIn) { dispatch( setLogin({ user: loggedIn.user, token: loggedIn.token, }) ); } toast.success("Successfully Logged In", { position: "top-right", autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "dark", transition: Bounce, }); } catch (error) { toast.error(error.response.data.message, { position: "top-right", autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "system", transition: Bounce, }); console.log(error.response.data); } };
app/forgot-password.js
"use client"; import ForgotPasswordRequest from "@/components/Auth/ForgotPasswordRequest"; import EmailSuccess from "@/components/misc/authModal/EmailSuccess"; import axios from "axios"; import { useTheme } from "next-themes"; import React, { useEffect, useState } from "react"; import { Bounce, toast } from "react-toastify"; const ForgotPassword = () => { const [email, setEmail] = useState(""); const theme = useTheme(); const [isEmailSent, setIsEmailSent] = useState(false); const verifyEmail = async () => { try { const result = await axios.post( "http://localhost:5000/api/auth/requestPasswordReset", { email } ); toast.success( "Password reset code has been sent successfully to your email", { position: "top-right", autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "system", transition: Bounce, } ); setIsEmailSent(true); console.log(result); } catch (error) { toast.error(error.response?.data.message, { position: "top-right", autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "system", transition: Bounce, }); console.log("error: ", error); } }; useEffect(() => { localStorage.setItem("email", email); }, [email]); return ( <> {!isEmailSent ? ( <ForgotPasswordRequest email={email} setEmail={setEmail} verifyEmail={verifyEmail} /> ) : ( <EmailSuccess email={email} /> )} </> ); }; export default ForgotPassword;
components/forgotPassword/ForgotPassword.jsx
"use client"; import React from "react"; import styles from "./auth.module.css"; import Link from "next/link"; import Logo from "../misc/Logo/Logo"; import { toast } from "react-toastify"; const ForgotPasswordRequest = ({ email, setEmail, verifyEmail }) => { return ( <div className={styles.auth_container_login}> <div className={styles.auth}> <div className={styles.auth_headers}> <div className={styles.upper_part}> <div className={styles.upper_part_text}></div>{" "} <div> <Logo /> </div> </div> <div className={styles.lower_part}>Find Your Account</div> </div> <div className={styles.auth_form}> <div className={styles.input_group}> <div className={styles.input}> <div> <label htmlFor=""> Please enter your email address or mobile number to search for your account. </label> </div> <div> <input type="email" placeholder="Enter your email" value={email} onChange={(e) => setEmail(e.target.value)} /> </div> </div> <div className={styles.btn}> <button onClick={verifyEmail}>Confirm your email address</button> </div> </div> <div className={styles.bottom_part}> <Link href="/">Go Back</Link> </div> </div> </div> </div> ); }; export default ForgotPasswordRequest;
components/forgotPassword/EmailSuccess.jsx
import React from "react"; import styles from "./emailSuccess.module.css"; import Image from "next/image"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faEnvelope } from "@fortawesome/free-solid-svg-icons"; import Link from "next/link"; import { ArrowLongLeftIcon } from "@heroicons/react/24/outline"; const EmailSuccess = ({ email }) => { const openEmailApp = () => { // Mailto link to open email client window.location.href = "mailto:"; }; return ( <div className={styles.container}> <div className={styles.card_email}> <div className={styles.card_top}> <FontAwesomeIcon icon={faEnvelope} shake size="3x" color="#03c03c" /> </div> <div className={styles.card_middle}> <div className={styles.text}>We sent a password reset link to</div> <div className={styles.text_email}>{email}</div> <div className={styles.btn}> <button onClick={openEmailApp}>Open email app</button> </div> </div> <div className={styles.card_bottom}> <div className={styles.text_resend}> Did not receive the email? <button>Click to resend</button> </div> <div className={styles.backTO}> <ArrowLongLeftIcon style={{ height: "1rem", width: "1rem", cursor: "pointer", marginRight: "10px", }} />{" "} <Link href="/">Back to login</Link> </div> </div> </div> </div> ); }; export default EmailSuccess;
app/forgot-password/reset/page.jsx
"use client"; import ResetPasswordLayout from "@/components/Auth/ResetPasswordLayout"; import PasswordResetSuccess from "@/components/misc/authModal/passwordResetSuccess/PasswordResetSuccess"; import React, { useEffect, useState } from "react"; import { Bounce, toast } from "react-toastify"; import axios from "axios"; import { useRouter, useSearchParams } from "next/navigation"; import { useTheme } from "next-themes"; import { useDispatch } from "react-redux"; import { handleLogin } from "@/services/api"; const ResetPassword = () => { const [isSuccess, setIsSuccess] = useState(false); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmpassword] = useState(""); const searchParams = useSearchParams(); const token = searchParams.get("token"); const userId = searchParams.get("id"); const [message, setMessage] = useState(""); const dispatch = useDispatch(); const router = useRouter(); const theme = useTheme(); useEffect(() => { if (password && confirmPassword && password === confirmPassword) { setMessage("Password Matched"); const hideTimeout = setTimeout(() => { setMessage(""); }, 3000); return () => clearTimeout(hideTimeout); } setEmail(localStorage.getItem("email")); }, [password, confirmPassword]); const resetPassword = async () => { try { const result = await axios.post( "http://localhost:5000/api/auth/resetPassword", { userId, token, password } ); toast.success("Password Successfully changed", { position: "top-right", autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "dark", transition: Bounce, }); console.log(result); setIsSuccess(true); } catch (error) { toast.error(error.response?.data.message, { position: "top-right", autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, theme: theme === "light" ? "light" : theme === "dark" ? "dark" : "system", transition: Bounce, }); } }; return ( <> {!isSuccess ? ( <ResetPasswordLayout password={password} confirmPassword={confirmPassword} setPassword={setPassword} setConfirmpassword={setConfirmpassword} resetPassword={resetPassword} message={message} /> ) : ( <PasswordResetSuccess handleLogin={() => handleLogin(email, password, dispatch, router, theme) } /> )} </> ); }; export default ResetPassword;
components/forgotPassword/ResetPasswordLayout.jsx
import React, { useEffect, useState } from "react"; import styles from "./auth.module.css"; import Link from "next/link"; import Logo from "../misc/Logo/Logo"; const ResetPasswordLayout = ({ password, confirmPassword, setPassword, setConfirmpassword, resetPassword, message, }) => { return ( <div className={styles.auth_container_login}> <div className={styles.auth}> <div className={styles.auth_headers}> <div className={styles.upper_part}> <div className={styles.upper_part_text}></div>{" "} <div> <Logo /> </div> </div> <div className={styles.lower_part}>Set New Password</div> <div className={styles.lower_part} style={{ fontSize: "12px", marginTop: "5px" }} > Your new password must be different from previously used password. </div> </div> <div className={styles.auth_form}> <div className={styles.input_group}> <div className={styles.input}> <div> <label htmlFor="">Password</label> </div> <div> <input type="password" placeholder="Enter your new password" value={password} onChange={(e) => setPassword(e.target.value)} /> </div> <div className={styles.input}> <div> <label htmlFor="">Confirm Password</label> </div> <div> <input type="password" placeholder="Confirm your new password" value={confirmPassword} onChange={(e) => setConfirmpassword(e.target.value)} /> </div> {password != confirmPassword && confirmPassword != "" ? ( <div className={styles.warning}> Password and Confirm password did not match </div> ) : confirmPassword != "" ? ( <div className={styles.success}>{message}</div> ) : null} </div> </div> <div className={styles.btn}> <button onClick={resetPassword}>Reset Password</button> </div> </div> <div className={styles.bottom_part}> <Link href="/">Back to login</Link> </div> </div> </div> </div> ); }; export default ResetPasswordLayout;
components/forgotPassword/PasswordResetSuccess.jsx
import React from "react"; import styles from "./passwordResetSuccess.module.css"; import { ArrowLongLeftIcon } from "@heroicons/react/24/outline"; import Link from "next/link"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCircleCheck } from "@fortawesome/free-solid-svg-icons"; const PasswordResetSuccess = ({ handleLogin }) => { return ( <div className={styles.container}> <div className={styles.card_email}> <div className={styles.card_top}> <FontAwesomeIcon icon={faCircleCheck} beat size="3x" color="#03c03c" />{" "} </div> <div className={styles.card_middle}> <div className={styles.bigText}>Password reset</div> <div className={styles.text}> Your password has been successfully reset. </div> <div className={styles.text_email}> Click below to login magically. </div> <div className={styles.btn}> <button onClick={handleLogin}>Continue</button> </div> </div> <div className={styles.card_bottom}> <div className={styles.backTO}> <ArrowLongLeftIcon style={{ height: "1rem", width: "1rem", cursor: "pointer", marginRight: "10px", }} />{" "} <Link href="/">Back to login</Link> </div> </div> </div> </div> ); }; export default PasswordResetSuccess;
**Conclusion
**In conclusion, implementing a forgot password feature in a MERN stack application using Next.js on the frontend and Node.js with Express on the backend involves creating secure routes for password reset requests and token management, integrating email notifications with Nodemailer, and ensuring robust data security with bcrypt for password hashing and token validation. This approach enhances user experience by providing a reliable mechanism for resetting passwords while maintaining data integrity and security throughout the process.
The above is the detailed content of How to forgot password using Mern stack with cloudinary cloud storage?. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics











JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

JavaScript is the core language of modern web development and is widely used for its diversity and flexibility. 1) Front-end development: build dynamic web pages and single-page applications through DOM operations and modern frameworks (such as React, Vue.js, Angular). 2) Server-side development: Node.js uses a non-blocking I/O model to handle high concurrency and real-time applications. 3) Mobile and desktop application development: cross-platform development is realized through ReactNative and Electron to improve development efficiency.

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

This article demonstrates frontend integration with a backend secured by Permit, building a functional EdTech SaaS application using Next.js. The frontend fetches user permissions to control UI visibility and ensures API requests adhere to role-base

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.

I built a functional multi-tenant SaaS application (an EdTech app) with your everyday tech tool and you can do the same. First, what’s a multi-tenant SaaS application? Multi-tenant SaaS applications let you serve multiple customers from a sing
