Sitemap

Secure Your App: Building an OTP-Based System with Node.js

2 min readJun 3, 2025

--

Hello Everyone.

This Node.js code uses the Express framework to build a simple OTP (One-Time Password) service.

It uses the built-in crypto module to generate a 6-digit OTP and stores it in Redis with a five-minute expiry.

The service exposes two main endpoints: one to request an OTP (where the generated OTP is stored in Redis under a key with the user ID and returned to the client), and one to verify the OTP (which retrieves the stored OTP from Redis, compares it with the user’s input, deletes it if it matches, and responds with a success or error message).

The service includes middleware to parse JSON request bodies, connects to Redis asynchronously, and listens for incoming requests on port 8080.

Overall, this code demonstrates using Redis for temporary storage of time-sensitive data (OTPs) and handling asynchronous operations with async/await in a typical Node.js web application.

Get Barath Kumar’s stories in your inbox

Join Medium for free to get updates from this writer.

package.json

{
"name": "OTPBasedSystem",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "mocha",
"start": "node index.js"
},
"keywords": [],
"author": "BarathKumar K",
"license": "ISC",
"dependencies": {
"body-parser": "^2.2.0",
"crypto": "^1.0.1",
"express": "^4.19.2",
"nodemon": "^3.1.0",
"otplib": "^12.0.1",
"redis": "^5.0.1"
}
}

Actual code

index.js

const express = require("express");
const app = express();
const crypto = require("crypto");
const {createClient} = require("redis");
const redisClient = createClient();
const OTP_EXPIRY = 300; // 5 mins
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
redisClient.connect().catch(console.error);

app.get("/", (req, res) => {
res.send("This is home page");
});

const generateOTP = async userId => {
const otp = crypto.randomInt(100000, 999999).toString();
const key = `otp:${userId}`;
await redisClient.setEx(key, OTP_EXPIRY, otp);
return otp;
}

const verifyOTP = async(userId, inputOtp) => {
const key = `otp:${userId}`;
const storedOtp = await redisClient.get(key);
if (!storedOtp) return false;
const valid = storedOtp === inputOtp;
if (valid) await redisClient.del(key); // One-time use
return ({ success: false, message: 'Invalid OTP' });
};

app.post('/otp/request', async (req, res) => {
console.log("Req.body : " + JSON.stringify(req.body, null, 4));
const userId = req.body.userId;
const otp = await generateOTP(userId);
res.json({ message: `OTP : ${otp}` });
});

app.post('/otp/verify', async(req, res) => {
const { userId, otp } = req.body;
const isValid = await verifyOTP(userId, otp);
if (isValid) return res.json({ success: true });
return res.status(400).json({ success: false, message: 'Invalid OTP' });
});

app.listen(8080, () => {
console.log("Running on the port 8080");
});

In conclusion, this Node.js OTP service is a straightforward and effective way to implement a secure, time-sensitive authentication mechanism using Redis for ephemeral data storage.

The application efficiently manages OTP generation and validation while ensuring that each OTP is single-use and expires automatically, enhancing security and user experience.

The use of modern async/await patterns and Express middleware makes the code both scalable and easy to maintain. This architecture can be readily expanded with additional features like rate limiting, user notifications, and logging to suit more complex production.

Leave a CLAP if you liked my article!!

--

--

Barath Kumar
Barath Kumar

Written by Barath Kumar

Backend Developer| NodeJS | AWS | GCP | SQL & NO SQL | Angular | 3X GCP