In this comprehensive tutorial, we'll walk you through the process of implementing "Forgot" and "Reset" password functionalities in a MERN (MongoDB, Express, React, Node.js) stack application. Losing access to an account due to a forgotten password is a common scenario, and our step-by-step instructions will help you build a secure and user-friendly password recovery system.

Starting with setting up the MongoDB database to store user information, we'll guide you through creating the necessary API endpoints in Express to handle password recovery requests. Learn how to integrate email verification for added security and generate secure password reset tokens.

On the front-end side, we'll develop React components that seamlessly integrate with the backend API to facilitate the password recovery flow. We'll also cover UI/UX best practices to provide a smooth user experience.

Throughout the tutorial, we'll emphasize security measures, including hashing and salting passwords, to protect user data from potential breaches.

By the end of this tutorial, you'll have a robust and secure password recovery system in your MERN stack application, ensuring your users' accounts are well-protected. Level up your MERN stack development skills with this valuable guide!

YouTube Link:

🎥 Forgot and Reset Password using MERN Stack


Let's Start:

First of all we should add forgot password link to the login page:

<Link to="/forgot-password">Forgot Password</Link>

I am using react-router-dom for routes after Link we will create route for that link:

<Route path="/forgot-password" element={<ForgotPassword />}></Route>

Now we will create the user interface for ForgotPassword Component:

import React from 'react'
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import axios from 'axios'


function ForgotPassword() {
    const [email, setEmail] = useState()
    const navigate = useNavigate()

    axios.defaults.withCredentials = true;
    const handleSubmit = (e) => {
        e.preventDefault()
        axios.post('http://localhost:3001/forgot-password', {email})
        .then(res => {
            if(res.data.Status === "Success") {
                navigate('/login')
               
            }
        }).catch(err => console.log(err))
    }

    return(
        <div className="d-flex justify-content-center align-items-center bg-secondary vh-100">
      <div className="bg-white p-3 rounded w-25">
        <h4>Forgot Password</h4>
        <form onSubmit={handleSubmit}>
          <div className="mb-3">
            <label htmlFor="email">
              <strong>Email</strong>
            </label>
            <input
              type="email"
              placeholder="Enter Email"
              autoComplete="off"
              name="email"
              className="form-control rounded-0"
              onChange={(e) => setEmail(e.target.value)}
            />
          </div>
          <button type="submit" className="btn btn-success w-100 rounded-0">
            Send
          </button>
          </form>
       
      </div>
    </div>
    )
}

export default ForgotPassword;

In this component we have an input field for email to enter the email after entering the email when user press the button then we will pass this email to the server side now we will implement server side code to handle forgot-password route and create and API:

Node.JS code:

This API will handle generate a token then it will send an email to the user:

app.post('/forgot-password', (req, res) => {
    const {email} = req.body;
    UserModel.findOne({email: email})
    .then(user => {
        if(!user) {
            return res.send({Status: "User not existed"})
        }
        const token = jwt.sign({id: user._id}, "jwt_secret_key", {expiresIn: "1d"})
        var transporter = nodemailer.createTransport({
            service: 'gmail',
            auth: {
              user: 'youremail@gmail.com',
              pass: 'your password'
            }
          });
         
          var mailOptions = {
            from: 'youremail@gmail.com',
            to: 'user email@gmail.com',
            subject: 'Reset Password Link',
            text: `http://localhost:5173/reset_password/${user._id}/${token}`
          };
         
          transporter.sendMail(mailOptions, function(error, info){
            if (error) {
              console.log(error);
            } else {
              return res.send({Status: "Success"})
            }
          });
    })
})

After this an email will sent to the user when user click on that link in email Reset password interface will open to him:

<Route path="/reset_password/:id/:token" element={<ResetPassword />}></Route>

The route for reset password page

import React from 'react'
import { useState } from "react";
import { Link, useNavigate, useParams } from "react-router-dom";
import axios from 'axios'


function ResetPassword() {
    const [password, setPassword] = useState()
    const navigate = useNavigate()
    const {id, token} = useParams()

    axios.defaults.withCredentials = true;
    const handleSubmit = (e) => {
        e.preventDefault()
        axios.post(`http://localhost:3001/reset-password/${id}/${token}`, {password})
        .then(res => {
            if(res.data.Status === "Success") {
                navigate('/login')
               
            }
        }).catch(err => console.log(err))
    }

    return(
        <div className="d-flex justify-content-center align-items-center bg-secondary vh-100">
      <div className="bg-white p-3 rounded w-25">
        <h4>Reset Password</h4>
        <form onSubmit={handleSubmit}>
          <div className="mb-3">
            <label htmlFor="email">
              <strong>New Password</strong>
            </label>
            <input
              type="password"
              placeholder="Enter Password"
              autoComplete="off"
              name="password"
              className="form-control rounded-0"
              onChange={(e) => setPassword(e.target.value)}
            />
          </div>
          <button type="submit" className="btn btn-success w-100 rounded-0">
            Update
          </button>
          </form>
       
      </div>
    </div>
    )
}

export default ResetPassword;


In the Reset Password component user will enter a new password and press the button after that it will move to the server side:

Node.JS

app.post('/reset-password/:id/:token', (req, res) => {
    const {id, token} = req.params
    const {password} = req.body

    jwt.verify(token, "jwt_secret_key", (err, decoded) => {
        if(err) {
            return res.json({Status: "Error with token"})
        } else {
            bcrypt.hash(password, 10)
            .then(hash => {
                UserModel.findByIdAndUpdate({_id: id}, {password: hash})
                .then(u => res.send({Status: "Success"}))
                .catch(err => res.send({Status: err}))
            })
            .catch(err => res.send({Status: err}))
        }
    })
})


It was all about this to implement.