DEV Community

Cover image for How to Generate and Verify OTPs in Next.js
Hadiza Mohammed
Hadiza Mohammed

Posted on

How to Generate and Verify OTPs in Next.js

When building modern web applications, One-Time Passwords (OTPs) are critical in two-factor authentication and user verification workflows. This guide will teach us how to generate, send, and verify OTPs using Next.js and a mock email service. By the end, you’ll understand how OTPs can enhance security and improve user experience.

What is an OTP?

An OTP (One-Time Password) is a temporary password used for a single login or transaction. It expires after a short time, providing additional security beyond regular passwords.

Common use cases:

  • Account Registration
  • Password Resets
  • Two-factor Authentication (2FA)

How OTP Generation Works

The general workflow for OTP generation and verification is:

  1. Generate a random OTP.
  2. Send the OTP to the user via email (or SMS).
  3. Verify the OTP when the user submits it.
  4. Activate or proceed with the next action only if OTP is correct.

Project Setup

To demonstrate OTP generation and verification, we’ll build:

  1. A backend API route to generate OTPs.
  2. A Redis store to temporarily save OTPs.
  3. An API route to verify OTPs.

Prerequisites:

1- Next.js project initialized:

 npx create-next-app@latest otp-nextjs-demo
 cd otp-nextjs-demo
Enter fullscreen mode Exit fullscreen mode

2- Install necessary dependencies:

 npm install redis nodemailer crypto
Enter fullscreen mode Exit fullscreen mode

Step 1: Set Up Redis

We’ll use Redis to temporarily store the OTP. For this guide, we’ll use Upstash, a serverless Redis service.

  1. Create a free Upstash Redis instance at https://upstash.com.
  2. Copy your Redis connection string and set it in your .env.local:
REDIS_URL=<your-upstash-redis-url>
EMAIL_USER=<your-email>
EMAIL_PASS=<your-email-password>
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the OTP Generation API

Next, let’s create an API route to generate and send OTPs.


// /pages/api/generateOTP.ts
import { NextApiRequest, NextApiResponse } from 'next';
import Redis from 'ioredis';
import crypto from 'crypto';
import nodemailer from 'nodemailer';

// Initialize Redis
const redis = new Redis(process.env.REDIS_URL!);

// Configure Nodemailer for sending emails
const transporter = nodemailer.createTransport({
  service: 'gmail', // Use your preferred email service
  auth: {
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASS,
  },
});

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { email } = req.body;

  if (!email) {
    return res.status(400).json({ message: 'Email is required' });
  }

  try {
    // Generate a 6-digit OTP
    const otp = crypto.randomInt(100000, 999999).toString();

    // Store OTP in Redis for 5 minutes
    await redis.setex(email, 300, otp);

    // Send OTP to user's email
    await transporter.sendMail({
      from: process.env.EMAIL_USER,
      to: email,
      subject: 'Your OTP Code',
      text: `Your OTP is ${otp}. It will expire in 5 minutes.`,
    });

    return res.status(200).json({ message: 'OTP sent to email' });
  } catch (error) {
    console.error('Error generating OTP:', error);
    return res.status(500).json({ message: 'Error generating OTP' });
  }
}

Enter fullscreen mode Exit fullscreen mode

How This Code Works:

  1. Redis Setup: We connect to Redis to store OTPs with a 5-minute expiration.
  2. OTP Generation: We use the crypto module to generate a random 6-digit OTP.
  3. Sending OTP via Email: Nodemailer is used to send the OTP to the user’s email.

Step 3: Verify OTP API

Now, let’s create the OTP verification endpoint.


// /pages/api/verifyOTP.ts
import { NextApiRequest, NextApiResponse } from 'next';
import Redis from 'ioredis';

// Initialize Redis
const redis = new Redis(process.env.REDIS_URL!);

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { email, otp } = req.body;

  if (!email || !otp) {
    return res.status(400).json({ message: 'Email and OTP are required' });
  }

  try {
    // Retrieve OTP from Redis
    const storedOtp = await redis.get(email);

    if (!storedOtp) {
      return res.status(400).json({ message: 'OTP expired or not found' });
    }

    if (storedOtp !== otp) {
      return res.status(400).json({ message: 'Invalid OTP' });
    }

    // OTP is valid; proceed with registration or action
    await redis.del(email); // Remove OTP after successful verification

    return res.status(200).json({ message: 'OTP verified successfully' });
  } catch (error) {
    console.error('Error verifying OTP:', error);
    return res.status(500).json({ message: 'Error verifying OTP' });
  }
}
Enter fullscreen mode Exit fullscreen mode

How This Code Works:

  1. OTP Retrieval: We retrieve the OTP from Redis using the provided email.
  2. OTP Validation: If the OTP matches, we delete it from Redis and return a successful response. If not, we return an error message.

Step 4: Frontend Form to Request OTP

Create a simple form to request an OTP.


// /pages/index.tsx
'use client';

import { useState } from 'react';

export default function Home() {
  const [email, setEmail] = useState('');
  const [message, setMessage] = useState('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    const res = await fetch('/api/generateOTP', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email }),
    });

    const data = await res.json();
    setMessage(data.message);
  };

  return (
    <div>
      <h1>Request OTP</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="email"
          placeholder="Enter your email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
        <button type="submit">Get OTP</button>
      </form>
      <p>{message}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Frontend Form to Verify OTP

Create another form to verify the OTP.


// /pages/verify.tsx
'use client';

import { useState } from 'react';

export default function Verify() {
  const [email, setEmail] = useState('');
  const [otp, setOtp] = useState('');
  const [message, setMessage] = useState('');

  const handleVerify = async (e: React.FormEvent) => {
    e.preventDefault();

    const res = await fetch('/api/verifyOTP', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, otp }),
    });

    const data = await res.json();
    setMessage(data.message);
  };

  return (
    <div>
      <h1>Verify OTP</h1>
      <form onSubmit={handleVerify}>
        <input
          type="email"
          placeholder="Enter your email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
        <input
          type="text"
          placeholder="Enter OTP"
          value={otp}
          onChange={(e) => setOtp(e.target.value)}
          required
        />
        <button type="submit">Verify OTP</button>
      </form>
      <p>{message}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this guide, we built a complete OTP generation and verification flow in Next.js. We used:

  • Redis to store OTPs temporarily.
  • Nodemailer to send OTPs via email.
  • API routes to generate and verify OTPs.

This workflow ensures that your application can securely verify users during registration or password resets. You can expand this by integrating OTP with two-factor authentication (2FA) for even better security.

Top comments (0)