Implementing OTP Verification in Node.js with MongoDB: A Step-by-Step Guide for Secure Authentication

Implementing OTP Verification in Node.js with MongoDB: A Step-by-Step Guide for Secure Authentication

Introduction

In the digital landscape, ensuring secure authentication mechanisms is paramount to safeguarding user data and maintaining trust. One such method gaining traction is one-time password (OTP) verification. In this comprehensive guide, we delve into building a robust OTP verification system using Node.js and MongoDB. By leveraging the power of these technologies, we'll explore how to create a seamless and secure authentication flow, bolstered by OTP verification. From generating and validating OTPs to storing user data securely in MongoDB, this tutorial equips developers with the knowledge and tools necessary to implement a reliable authentication solution. Whether you're securing a web application, API, or mobile app, mastering OTP verification with Node.js and MongoDB is essential for ensuring the integrity and confidentiality of user accounts.

Step 1: Create a new directory for your project:

mkdir otp-verification-system
cd otp-verification-system

Step 2: Initialize a new Node.js project.

npm init -y

Step 3: Install the required dependencies:

npm install express mongoose twilio dotenv body-parser
  1. Express: As a fast, unopinionated, minimalist web framework for Node.js, Express simplifies the process of building web applications and APIs by providing robust features and middleware for routing, request handling, and more.

  2. Mongoose: Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment. It enables developers to define schemas, interact with MongoDB databases, perform validations, and create complex queries, making MongoDB data manipulation more intuitive and efficient.

  3. Twilio: Twilio is a cloud communications platform that enables developers to integrate messaging, voice, and video functionalities into their applications via APIs. Installing the Twilio package allows for seamless integration of SMS-based OTP verification, enhancing the security and user experience of authentication systems.

  4. dotenv: The dotenv module loads environment variables from a.env file into process.env, allowing developers to manage configuration variables securely and easily across different environments. It helps keep sensitive information like API keys, database URIs, and authentication tokens out of version control and readily accessible to the application.

  5. Body-parser: Body-parser is a middleware for Express.js that parses incoming request bodies in various formats, such as JSON, URL-encoded, and multipart. It extracts the data payload from HTTP requests, making it accessible within route handlers for processing, validation, and manipulation. This middleware is crucial for handling form submissions, API payloads, and other types of data sent to the server.

Step 4: Setting Up MongoDB:

First, let's create our MongoDB database. Create a file called db.js in your project directory and add the following code:

const mongoose = require('mongoose');

function connect() {
  return new Promise((resolve, reject) => {
    mongoose.connect('mongodb://localhost/otp_verification', {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });

    const db = mongoose.connection;

    db.on('error', error => {
      console.error('MongoDB connection error:', error);
      reject(error);
    });

    db.once('open', () => {
      console.log('Connected to MongoDB');
      resolve();
    });
  });
}

module.exports = { connect };

This code establishes a connection to a MongoDB database named "otp_verification" running locally, using Mongoose.

Step 5: Creating the User Model:

Next, we'll develop a user model to store user data. Create a file called models/User.js and include the following code:

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  username: String,
  phone: String,
  otp: String,
  otpExpiration: Date,
});

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

This code defines a Mongoose schema for a user entity with fields for username, phone number, OTP (One-Time Password), and OTP expiration date. It exports a Mongoose model named "User" based on this schema, allowing interaction with the MongoDB database.

Step 6: Generating and Sending OTP:

Now, let's build the logic for generating and sending OTP via SMS. We'll be using the Twilio API for this. Create a file called utils/otp.js and include the following code:

const twilio = require('twilio');
const dotenv = require('dotenv');

dotenv.config();

const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const client = new twilio(accountSid, authToken);

async function sendOTP(phone, otp) {
  try {
    const message = await client.messages.create({
      body: `Your OTP is: ${otp}`,
      from: process.env.TWILIO_PHONE_NUMBER,
      to: phone,
    });
    console.log(`OTP sent to ${phone}: ${message.sid}`);
  } catch (error) {
    console.error('Error sending OTP:', error);
  }
}

module.exports = { sendOTP };

Make sure you have created a Twilio account and acquired the required credentials, which need to be kept in a.env file (Account SID, Auth Token, and Phone Number).

Step 7: Implementing the OTP Verification Endpoint:

Now, let’s create an endpoint to handle OTP verification. Create a file named routes/auth.js and add the following code:

const express = require('express');
const router = express.Router();
const User = require('../models/User');
const { sendOTP } = require('../utils/otp');
const {randomInt} = require('crypto')

router.post('/sendotp', async (req, res) => {
  const { phone } = req.body;

  // Generate a 6-digit OTP
  //const otp = Math.floor(100000 + Math.random() * 900000).toString();
  const otp =  randomInt(100000, 999999);

  try {
    // Save OTP and its expiration time in the database
    const user = await User.findOneAndUpdate(
      { phone },
      { otp, otpExpiration: Date.now() + 600000 }, // OTP expires in 10 minutes
      { upsert: true, new: true }
    );

    // Send OTP via SMS
    await sendOTP(phone, otp);

    res.status(200).json({ success: true, message: 'OTP sent successfully' });
  } catch (error) {
    console.error('Error sending OTP:', error);
    res.status(500).json({ success: false, message: 'Failed to send OTP' });
  }
});

router.post('/verifyotp', async (req, res) => {
  const { phone, otp } = req.body;

  try {
    // Find user by phone number and OTP
    const user = await User.findOne({ phone, otp });

    if (!user || user.otpExpiration < Date.now()) {
      return res.status(400).json({ success: false, message: 'Invalid OTP' });
    }

    // Clear OTP and expiration time after successful verification
    user.otp = undefined;
    user.otpExpiration = undefined;
    await user.save();

    res.status(200).json({ success: true, message: 'OTP verified successfully' });
  } catch (error) {
    console.error('Error verifying OTP:', error);
    res.status(500).json({ success: false, message: 'Failed to verify OTP' });
  }
});

module.exports = router;

Two endpoints are defined by this code: /sendotp, which generates and sends OTP, and /verifyotp, which checks the OTP that the user entered.

Step 8: Starting the Server:

Finally, let’s create the main server file to start our application. Create a file named server.js and add the following code:

const express = require('express');
const bodyParser = require('body-parser');
const { connect } = require('./db'); // Import the connect function from db.js
const authRoutes = require('./routes/auth');

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

// Middleware
app.use(bodyParser.json());

// Routes
app.use('/auth', authRoutes);

// Connect to the database before starting the server
connect()
  .then(() => {
    // Start server
    app.listen(PORT, () => {
      console.log(`Server is running on port ${PORT}`);
    });
  })
  .catch(error => {
    console.error('Error connecting to database:', error);
  });

Conclusion

In conclusion, we've delved into the intricate process of building an OTP (One-Time Password) verification system using Node.js and MongoDB. By leveraging key technologies like Express for server handling, Mongoose for database interactions, and Twilio for SMS messaging, we've crafted a robust solution for secure user authentication. Through this guide, you've gained insights into the essential steps required to implement OTP verification, from setting up the database schema to integrating SMS-based authentication. Armed with this knowledge, you're now equipped to enhance the security and reliability of your applications by implementing effective OTP verification systems.