Project Structure(Backend)

Backend Folder Structure

The updated dependencies in package.json file of backend will look like:

"dependencies": {
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"dotenv": "^16.4.5",
"express": "^4.18.3",
"mysql2": "^3.9.2",
"nodemailer": "^6.9.12"
}

Step 4: Create a .env file in your backend’s root folder and put your email id and password inside it to hide them.

PORT=4000
DATABASE_HOST=localhost
DATABSE_USER=<your_db_username>
DATABSE_PASSWORD=<your_db_password>
DATABASE_PORT=3306
DATABASE_NAME=users
DATABASE_CONNECTION_LIMIT=20
EMAIL_ID=<your_email>
EMAIL_PASSWORD=<your_email_app_password>
FORGOT_PASSWORD_TOKEN_SECRET=somesecret
FRONTEND_URL=http://localhost:3000
NO_OF_SALT_ROUNDS=10

Step 5: Create the files `server.js`, `routes.js`, `email.js` and `db.js` which will run our Express Server and serve the React App with responses to routes hit.

Javascript
//server.js

require("dotenv").config();

const express = require("express");
const cors = require("cors");
const Route = require("./routes/route");

const app = express();
const PORT = process.env.PORT || 4000;

app.use(express.json());
app.use(
  cors({
    origin: "http://localhost:3000",
    methods: ["GET", "POST"],
  })
);

app.use("/api", Route);

app.listen(PORT, () => {
  console.log(`Server started on PORT ${PORT}`);
});
Javascript
// db.js

require("dotenv").config();

const mysql = require("mysql2");

const pool = mysql.createPool({
  host: process.env.DATABASE_HOST,
  port: process.env.DATABASE_PORT,
  user: process.env.DATABSE_USER,
  password: process.env.DATABSE_PASSWORD,
  database: process.env.DATABASE_NAME,
  connectionLimit: process.env.DATABASE_CONNECTION_LIMIT,
});

let db = {};

db.get_user_by_email = (email) => {
  const query = `SELECT email, id from details WHERE email = "${email}";`;
  return execute(query);
};

db.update_forgot_password_token = (id, token) => {
  const createdAt = new Date().toISOString();
  const expiresAt = new Date(Date.now() + 60 * 60 * 24 * 1000).toISOString();
  const query = `INSERT INTO reset_tokens(token, created_at, expires_at, user_id) VALUES('${token}', '${createdAt}', '${expiresAt}', ${id})`;
  return execute(query);
};

db.get_password_reset_token = (id) => {
  const query = `SELECT token, expires_at from reset_tokens WHERE user_id = ${id} ORDER BY created_at DESC LIMIT 1;`;
  return execute(query);
};

db.update_password_reset_token = (id) => {
  const query = `DELETE FROM reset_tokens WHERE user_id = ${id}`;
  return execute(query);
};

db.update_user_password = (id, password) => {
  const query = `UPDATE details SET password = '${password}' WHERE id = ${id}`;
  return execute(query);
};

const execute = (query) => {
  return new Promise((resolve, reject) => {
    pool.query(query, (err, results) => {
      if (err) return reject(err);
      return resolve(results);
    });
  });
};

module.exports = db;
Javascript
// ./utils/email.js

require("dotenv").config();

const nodemailer = require("nodemailer");

const sendEmail = async (option) => {
  try {
    const transporter = nodemailer.createTransport({
      service: "gmail",
      auth: {
        user: process.env.EMAIL_ID,
        pass: process.env.EMAIL_PASSWORD,
      },
    });
    const mailOption = {
      from: process.env.EMAIL_ID,
      to: option.email,
      subject: option.subject,
      html: option.message,
    };
    await transporter.sendMail(mailOption, (err, info) => {
      if (err) console.log(err);
    });
  } catch (err) {
    console.log(err);
  }
};

const mailTemplate = (content, buttonUrl, buttonText) => {
  return `<!DOCTYPE html>
  <html>
  <body style="text-align: center; font-family: 'Verdana', serif; color: #000;">
    <div
      style="
        max-width: 400px;
        margin: 10px;
        background-color: #fafafa;
        padding: 25px;
        border-radius: 20px;
      "
    >
      <p style="text-align: left;">
        ${content}
      </p>
      <a href="${buttonUrl}" target="_blank">
        <button
          style="
            background-color: #444394;
            border: 0;
            width: 200px;
            height: 30px;
            border-radius: 6px;
            color: #fff;
          "
        >
          ${buttonText}
        </button>
      </a>
      <p style="text-align: left;">
        If you are unable to click the above button, copy paste the below URL into your address bar
      </p>
      <a href="${buttonUrl}" target="_blank">
          <p style="margin: 0px; text-align: left; font-size: 10px; text-decoration: none;">
            ${buttonUrl}
          </p>
      </a>
    </div>
  </body>
</html>`;
};

module.exports = { sendEmail, mailTemplate };
Javascript
// ./routes/route.js

require("dotenv").config();

const express = require("express");
const crypto = require("crypto");
const bcrypt = require("bcrypt");
const router = express.Router();

const db = require("../db");
const { sendEmail, mailTemplate } = require("../utils/email");

const NumSaltRounds = Number(process.env.NO_OF_SALT_ROUNDS);

router.post("/forgotPassword", async (req, res) => {
  try {
    const email = req.body.email;
    const user = await db.get_user_by_email(email);

    if (!user || user.length === 0) {
      res.json({
        success: false,
        message: "Your are not registered!",
      });
    } else {
      const token = crypto.randomBytes(20).toString("hex");
      const resetToken = crypto
        .createHash("sha256")
        .update(token)
        .digest("hex");
      await db.update_forgot_password_token(user[0].id, resetToken);

      const mailOption = {
        email: email,
        subject: "Forgot Password Link",
        message: mailTemplate(
          "We have received a request to reset your password. Please reset your password using the link below.",
          `${process.env.FRONTEND_URL}/resetPassword?id=${user[0].id}&token=${resetToken}`,
          "Reset Password"
        ),
      };
      await sendEmail(mailOption);
      res.json({
        success: true,
        message: "A password reset link has been sent to your email.",
      });
    }
  } catch (err) {
    console.log(err);
  }
});

router.post("/resetPassword", async (req, res) => {
  try {
    const { password, token, userId } = req.body;
    const userToken = await db.get_password_reset_token(userId);
    if (!res || res.length === 0) {
      res.json({
        success: false,
        message: "Some problem occurred!",
      });
    } else {
      const currDateTime = new Date();
      const expiresAt = new Date(userToken[0].expires_at);
      if (currDateTime > expiresAt) {
        res.json({
          success: false,
          message: "Reset Password link has expired!",
        });
      } else if (userToken[0].token !== token) {
        res.json({
          success: false,
          message: "Reset Password link is invalid!",
        });
      } else {
        await db.update_password_reset_token(userId);
        const salt = await bcrypt.genSalt(NumSaltRounds);
        const hashedPassword = await bcrypt.hash(password, salt);
        await db.update_user_password(userId, hashedPassword);
        res.json({
          success: true,
          message: "Your password reset was successfully!",
        });
      }
    }
  } catch (err) {
    console.log(err);
  }
});

module.exports = router;

Step 12: Assuming that we are using MySQL database which contains `users` database with two tables `details` and `reset_tokens` whose schema is as follows:

CREATE TABLE details
(
id int auto_increment primary key,
email varchar(255) not null unique,
name varchar(255) not null,
password varchar(255) not null
);

create table reset_tokens
(
token varchar(255) not null,
created_at varchar(255) not null,
expires_at varchar(255) not null,
user_id int not null,
PRIMARY KEY(user_id, token)
);

To start the backend server run the following command:

node server.js

Output:

Forgot and Reset Password with React and Node JS



Forgot & Reset Password Feature with React and Node JS

Almost all the websites that you use nowadays require you to signup and then login. But, with the tremendous rise in number of websites being used by you, it is difficult for you to remember the passwords for so many websites if you use different passwords on different websites.

Ever had that “Oh no, I forgot my password” moment? Fear not! The “Forgot and Reset Password” feature on these website acts as a life saver for you. So, the next time you draw a blank on your password, just click that “Forgot Password” link and reset your password. In this article, we will look at how we can implement this most important funcationality in any website i.e. Forgot and Reset Password functionality using React and NodeJS.

Output Preview: Let us have a look at how the final output will look like

Output preview

Similar Reads

Prerequisites:

NPM & NodeJSReactJSReact-Material UIReact Hooks...

Approach to Create Forgot and Reset Password with React and NodeJS:

List all the requirements for the project and create the folder structure of the project accordingly.Choose and install the required dependencies necessary for the project.Define your database schema and table structure.Create different routes and pages for login, forgot password and reset password.When user clicks on “Forgot Password” on the Login page, the user gets redirected to ForgotPassword page where the user is asked to enter his/her email id.Once the user clicks the “Reset Password’ button, a request goes to backend, where the server generates a token and sends the reset password link to the user’s email id.When the user clicks on the Reset Password Link received in the email, he is asked to enter a new password. If user clicks submit, token validation will be done at backend and the new hashed password will be stored in database. The server then sends a success status indicating password reset was successful....

Steps to Setup a React App:

Step 1: Create a new React App project by opening a terminal and using the following command:...

Project Structure(Frontend):

React Folder Structure...

Steps to Create a NodeJS App and Installing Module:

Step 1: Create a npm project inside a new folder `backend` folder as follows:...

Project Structure(Backend):

Backend Folder Structure...