Implement SMS OTP 2FA in Node.js and Express with Vonage Verify
Two-Factor Authentication (2FA) adds a critical layer of security to applications by requiring users to provide two distinct forms of identification before granting access. One common and user-friendly method is One-Time Passwords (OTP) delivered via SMS.
This guide provides a step-by-step walkthrough for building a production-ready SMS OTP 2FA system using Node.js, Express, and the Vonage Verify API. We'll cover everything from initial project setup to deployment considerations.
Project Goals:
- Build a simple web application with Node.js and Express.
- Implement a user flow where a user enters their phone number to receive an SMS OTP.
- Verify the OTP entered by the user using the Vonage Verify API.
- Securely handle API credentials.
- Implement basic error handling and user feedback.
Technologies Used:
- Node.js: A JavaScript runtime environment for server-side development.
- Express: A minimal and flexible Node.js web application framework.
- Vonage Verify API: A service that handles OTP generation, delivery (SMS, voice), and verification. We use this to avoid building and maintaining the complex OTP logic ourselves.
- dotenv: A module to load environment variables from a
.env
file intoprocess.env
. - EJS (Embedded JavaScript templates): A simple templating engine to generate HTML markup with plain JavaScript.
System Architecture:
The flow involves three main components: the user's browser (Client), our Node.js/Express application (Server), and the Vonage Verify API.
+--------+ 1. Enter Phone Number +--------+ 3. Send OTP SMS +--------------+
| Client | --------------------------------> | Server | --------------------------> | Vonage Verify|
+--------+ (POST /request-otp) +--------+ (API Request) +--------------+
| ^ | |
| | | 4. Return request_id | 5. SMS to User
| 2. Render OTP Entry Form | v v
| (Pass request_id) +---------------------------------------+--------+
| | User's |
| | Phone |
+--------+ 6. Submit OTP + request_id +--------+ +--------+
| Client | --------------------------------> | Server | 8. Verify OTP +--------------+
+--------+ (POST /verify-otp) +--------+ --------------------------> | Vonage Verify|
| ^ | (API Request) +--------------+
| | | |
| 7. Render Success/Failure Page | | 9. Return Verification Result |
| | v |
+------------------------------------------+----------------------------------------+
Prerequisites:
- Node.js and npm (or yarn): Installed on your development machine. (Download Node.js)
- Vonage API Account: Sign up for a free account at Vonage API Dashboard. You get free credit to start.
- Vonage API Key and Secret: Found at the top of your Vonage API Dashboard after signing up.
Final Outcome:
By the end of this guide, you will have a functional Node.js application that can:
- Present a form to enter a phone number.
- Use the Vonage Verify API to send an OTP to that number via SMS.
- Present a form to enter the received OTP.
- Verify the entered OTP against the Vonage Verify API.
- Display a success or failure message to the user.
You'll also have a foundational understanding of integrating Vonage Verify, handling credentials, basic error scenarios, and deployment considerations.
1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
1. Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it:
mkdir vonage-otp-app
cd vonage-otp-app
2. Initialize Node.js Project:
Create a package.json
file to manage dependencies and project metadata:
npm init -y
3. Install Dependencies:
We need Express for the web server, the Vonage Node SDK, EJS for templating, and dotenv
for managing environment variables.
npm install express @vonage/server-sdk dotenv ejs
4. Create Project Structure: Set up a basic directory structure for clarity:
vonage-otp-app/
├── views/
│ ├── index.ejs
│ ├── verify.ejs
│ └── result.ejs
├── .env
├── .gitignore
├── app.js
├── package.json
└── package-lock.json
5. Configure Environment Variables (.env
):
Create a file named .env
in the project root. Add your Vonage API Key and Secret, which you obtained from the Vonage dashboard.
# .env
VONAGE_API_KEY=YOUR_API_KEY
VONAGE_API_SECRET=YOUR_API_SECRET
VONAGE_BRAND_NAME="My Awesome App" # Optional: Brand name shown in the SMS message
VONAGE_API_KEY
: Your API key from the Vonage Dashboard.VONAGE_API_SECRET
: Your API secret from the Vonage Dashboard.VONAGE_BRAND_NAME
: The name included in the verification message (e.g., "Your My Awesome App code is: 1234").
6. Create .gitignore
:
It's crucial to prevent accidentally committing sensitive information like your .env
file or node_modules
. Create a .gitignore
file in the root directory:
# .gitignore
node_modules
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*
This setup provides a clean structure and ensures your credentials remain secure.
2. Implementing Core Functionality and API Routes
Now, let's write the core application logic in app.js
, including initializing Express, setting up the Vonage SDK, defining routes, and handling the OTP request and verification flows.
app.js
:
// app.js
require('dotenv').config(); // Load environment variables from .env file
const express = require('express');
const path = require('path');
const { Vonage } = require('@vonage/server-sdk');
const app = express();
const port = process.env.PORT || 3000; // Use environment port or default to 3000
// --- Middleware ---
app.use(express.json()); // Parse JSON request bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded request bodies
app.set('view engine', 'ejs'); // Set EJS as the templating engine
app.set('views', path.join(__dirname, 'views')); // Specify the views directory
// --- Initialize Vonage ---
// Validate that API Key and Secret are set
if (!process.env.VONAGE_API_KEY || !process.env.VONAGE_API_SECRET) {
console.error('Error: VONAGE_API_KEY and VONAGE_API_SECRET must be set in .env file.');
process.exit(1); // Exit if credentials are missing
}
const vonage = new Vonage({
apiKey: process.env.VONAGE_API_KEY,
apiSecret: process.env.VONAGE_API_SECRET
});
const vonageBrand = process.env.VONAGE_BRAND_NAME || 'MyApp'; // Use brand from .env or default
// --- In-memory storage (for demo purposes) ---
// !! IMPORTANT: In a production app, use a database or persistent cache (e.g., Redis)
// !! to store request_id associated with user session or identifier.
let verifyRequestId = null;
// --- Routes ---
// GET / : Render the initial form to enter phone number
app.get('/', (req, res) => {
res.render('index', { error: null }); // Pass null error initially
});
// POST /request-otp : Request an OTP from Vonage
app.post('/request-otp', async (req, res) => {
const phoneNumber = req.body.phoneNumber;
// Basic validation: Check if phone number is provided
if (!phoneNumber) {
return res.render('index', { error: 'Phone number is required.' });
}
console.log(`Requesting OTP for number: ${phoneNumber}`);
try {
const result = await vonage.verify.start({
number: phoneNumber,
brand: vonageBrand,
workflow_id: 1 // Use SMS -> TTS -> TTS workflow
// code_length: 6 // Optional: Specify OTP length (default is 4)
// pin_expiry: 300 // Optional: Specify expiry time in seconds (default is 300)
});
console.log('Vonage Verify API Response:', result);
if (result.status === '0') {
// Store the request_id (IMPORTANT: Associate with user session in production)
verifyRequestId = result.request_id;
console.log(`Verification request sent. Request ID: ${verifyRequestId}`);
// Render the verification form, passing the request_id
res.render('verify', { requestId: verifyRequestId, error: null });
} else {
// Handle Vonage API errors (e.g., invalid number, throttling)
console.error('Vonage Verify Start Error:', result.error_text);
res.render('index', { error: `Error starting verification: ${result.error_text} (Status: ${result.status})` });
}
} catch (error) {
console.error('Error calling Vonage Verify API:', error);
res.render('index', { error: 'An unexpected error occurred. Please try again.' });
}
});
// POST /verify-otp : Verify the OTP entered by the user
app.post('/verify-otp', async (req, res) => {
const otpCode = req.body.otpCode;
const requestId = req.body.requestId; // Get requestId from the hidden input
// Basic validation
if (!otpCode) {
return res.render('verify', { requestId: requestId, error: 'OTP code is required.' });
}
if (!requestId) {
// This shouldn't happen if the form is submitted correctly, but good to check
console.error('Error: Missing requestId during verification.');
return res.render('index', { error: 'Verification session expired or invalid. Please request a new code.' });
}
console.log(`Verifying OTP code: ${otpCode} for Request ID: ${requestId}`);
try {
const result = await vonage.verify.check(requestId, otpCode);
console.log('Vonage Verify Check Response:', result);
if (result.status === '0') {
// Verification successful
console.log(`Verification successful for Request ID: ${requestId}`);
verifyRequestId = null; // Clear the request ID after successful verification
res.render('result', { success: true, message: 'Phone number verified successfully!' });
} else {
// Handle verification errors (e.g., wrong code, expired code)
console.error('Vonage Verify Check Error:', result.error_text);
// Determine if the error is likely due to a wrong code (status 16) or expiry/too many attempts (status 6)
let errorMessage = `Verification failed: ${result.error_text} (Status: ${result.status})`;
if (result.status === '16') {
errorMessage = 'Incorrect OTP code entered. Please try again.';
} else if (result.status === '6') {
errorMessage = 'Verification request expired or too many attempts. Please request a new code.';
// Optionally redirect back to the start if expired
// return res.render('index', { error: errorMessage });
}
// Re-render the verify page with the error
res.render('verify', { requestId: requestId, error: errorMessage });
}
} catch (error) {
// Handle potential network errors or SDK issues
console.error('Error calling Vonage Verify Check API:', error);
// Re-render verify page with a generic error, preserving requestId
res.render('verify', { requestId: requestId, error: 'An unexpected error occurred during verification. Please try again.' });
}
});
// --- Start Server ---
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
// --- Export app for testing ---
// This line is needed for the integration tests (Section 12)
module.exports = app;
Explanation:
- Dependencies & Setup: Loads
dotenv
, requiresexpress
and@vonage/server-sdk
, initializes Express, sets up middleware (JSON/URL-encoded parsers, EJS view engine). - Vonage Initialization: Creates a
Vonage
instance using credentials from.env
. Includes a check to ensure credentials exist. - In-Memory Storage: A simple
verifyRequestId
variable is used. This is crucial: In a real application, you must store thisrequest_id
securely, associating it with the user's session or another identifier, likely in a database or cache (like Redis). Storing it globally like this only works for a single-user demo. GET /
: Renders the initialindex.ejs
view.POST /request-otp
:- Retrieves the
phoneNumber
from the request body. - Performs basic validation.
- Calls
vonage.verify.start()
with the phone number and brand name. - Crucially, if
result.status
is '0' (success), it stores theresult.request_id
and renders theverify.ejs
view, passing therequestId
to it. - Handles Vonage API errors by re-rendering the
index.ejs
view with an error message. - Includes a
try...catch
block for network or SDK errors.
- Retrieves the
POST /verify-otp
:- Retrieves the
otpCode
andrequestId
from the request body (therequestId
comes from a hidden field in theverify.ejs
form). - Performs basic validation.
- Calls
vonage.verify.check()
with therequestId
andotpCode
. - If
result.status
is '0', verification is successful. It clears the storedrequestId
and rendersresult.ejs
with a success message. - Handles Vonage verification errors (like wrong code - status 16, or expired/too many attempts - status 6) by re-rendering
verify.ejs
with therequestId
and an appropriate error message. - Includes a
try...catch
block.
- Retrieves the
- Server Start: Starts the Express server.
- Export: Exports the
app
instance for use in integration tests.
API Endpoint Summary:
GET /
:- Purpose: Renders the initial phone number input form.
- Request: None
- Response: HTML page (
index.ejs
)
POST /request-otp
:- Purpose: Initiates the OTP verification process with Vonage.
- Request Body:
application/x-www-form-urlencoded
orapplication/json
phoneNumber
: (String) The user's phone number in E.164 format (e.g.,14155552671
).
- Response: HTML page (
verify.ejs
on success with hiddenrequestId
,index.ejs
on error) curl
Example:curl -X POST http://localhost:3000/request-otp \ -H ""Content-Type: application/x-www-form-urlencoded"" \ -d ""phoneNumber=YOUR_PHONE_NUMBER"" # Replace YOUR_PHONE_NUMBER with a valid E.164 number
POST /verify-otp
:- Purpose: Checks the user-submitted OTP against the Vonage request.
- Request Body:
application/x-www-form-urlencoded
orapplication/json
otpCode
: (String) The 4 or 6-digit code entered by the user.requestId
: (String) Therequest_id
received from the/request-otp
step.
- Response: HTML page (
result.ejs
on success,verify.ejs
on error) curl
Example (requires a validrequestId
from a previous step):curl -X POST http://localhost:3000/verify-otp \ -H ""Content-Type: application/x-www-form-urlencoded"" \ -d ""otpCode=1234&requestId=YOUR_REQUEST_ID"" # Replace 1234 with the actual OTP and YOUR_REQUEST_ID
3. Creating the Views (EJS Templates)
Now, let's create the simple HTML forms using EJS in the views
directory.
views/index.ejs
(Initial Form):
<!-- views/index.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width_ initial-scale=1.0">
<title>Enter Phone Number</title>
<style> /* Basic styling */
body { font-family: sans-serif; padding: 20px; }
.error { color: red; margin-bottom: 10px; }
label, input, button { display: block; margin-bottom: 10px; }
input { padding: 8px; width: 250px; }
button { padding: 10px 15px; cursor: pointer; }
</style>
</head>
<body>
<h1>Enter Your Phone Number</h1>
<p>We will send an SMS with a verification code.</p>
<% if (error) { %>
<p class="error"><%= error %></p>
<% } %>
<form action="/request-otp" method="post">
<label for="phoneNumber">Phone Number (E.164 format, e.g., 14155552671):</label>
<input type="tel" id="phoneNumber" name="phoneNumber" required placeholder="14155552671">
<button type="submit">Send Code</button>
</form>
</body>
</html>
views/verify.ejs
(OTP Entry Form):
<!-- views/verify.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width_ initial-scale=1.0">
<title>Enter Verification Code</title>
<style>/* Basic styling */
body { font-family: sans-serif; padding: 20px; }
.error { color: red; margin-bottom: 10px; }
label, input, button { display: block; margin-bottom: 10px; }
input { padding: 8px; width: 100px; }
button { padding: 10px 15px; cursor: pointer; }
</style>
</head>
<body>
<h1>Enter Verification Code</h1>
<p>Enter the code sent to your phone.</p>
<% if (error) { %>
<p class="error"><%= error %></p>
<% } %>
<form action="/verify-otp" method="post">
<input type="hidden" name="requestId" value="<%= requestId %>"> <!-- Crucial: Pass requestId -->
<label for="otpCode">OTP Code:</label>
<input type="text" id="otpCode" name="otpCode" required pattern="\d{4_6}" title="Enter the 4 or 6 digit code">
<button type="submit">Verify Code</button>
</form>
<p><a href=" />Request a new code</a></p>
</body>
</html>
views/result.ejs
(Success/Failure Page):
<!-- views/result.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width_ initial-scale=1.0">
<title>Verification Result</title>
<style>/* Basic styling */
body { font-family: sans-serif; padding: 20px; }
.success { color: green; }
.failure { color: red; } /* You might add failure case here if needed */
</style>
</head>
<body>
<h1>Verification Result</h1>
<% if (success) { %>
<p class="success"><%= message %></p>
<% } else { %>
<p class="failure"><%= message %></p>
<% } %>
<p><a href=" />Start Over</a></p>
</body>
</html>
These templates provide the user interface for interacting with our backend API endpoints. Note how verify.ejs
includes a hidden input field to pass the requestId
back to the server during the verification step.
4. Integrating with Vonage (Configuration Details)
We've already initialized the SDK in app.js
, but let's reiterate the configuration steps and where to find the details.
- Sign Up/Log In: Go to the Vonage API Dashboard.
- Find API Key and Secret: On the main dashboard page (""Getting started""), your API Key and API Secret are displayed prominently near the top.
- API Key (
VONAGE_API_KEY
): A public identifier for your account. - API Secret (
VONAGE_API_SECRET
): A private credential used to authenticate your requests. Treat this like a password (important) - do not share it or commit it to version control.
- API Key (
- Store Credentials Securely: Copy the Key and Secret into your
.env
file as shown in Step 1. Thedotenv
library loads these intoprocess.env
, allowing yourapp.js
to access them securely without hardcoding. - Brand Name (
VONAGE_BRAND_NAME
): This optional variable in.env
sets thebrand
parameter in thevonage.verify.start
call. This name appears in the SMS message template (e.g., ""Your [Brand Name] code is 1234""). If not set, it defaults to""MyApp""
in our code. - No Fallback Needed (Verify API handles it): The Vonage Verify API itself manages retries and fallback mechanisms (like Text-to-Speech calls if SMS fails or times out, depending on the
workflow_id
used). You don't need to implement SMS delivery fallbacks yourself when using Verify.
5. Implementing Error Handling and Logging
Our app.js
includes basic error handling, but let's detail the strategy.
- Consistent Strategy: Use
try...catch
blocks around all external API calls (Vonage) and potentially problematic operations. - User Feedback: When an error occurs, re-render the relevant form (
index.ejs
orverify.ejs
) and pass anerror
variable containing a user-friendly message. Avoid exposing raw API error details directly to the user unless necessary (like "Invalid phone number format"). - Server-Side Logging: Use
console.log
andconsole.error
to log detailed information about requests, successful operations, and especially errors, including the full error object or specific Vonageerror_text
andstatus
. In production, use a dedicated logging library (like Winston or Pino) to structure logs and send them to a centralized logging system (e.g., Datadog, Logstash, CloudWatch).- Example (inside a
catch
block):catch (error) { // Log detailed error for debugging console.error(`[${new Date().toISOString()}] Error in /verify-otp for Request ID ${requestId}:`, error); // Provide user-friendly message res.render('verify', { requestId: requestId, error: 'An unexpected error occurred during verification. Please try again.' }); }
- Example (inside a
- Vonage Status Codes: The primary way Vonage signals issues is through the
status
code in the API response (outside of network errors).status: '0'
means success.- Any non-zero status indicates an error. Check the
error_text
for details. - Common Verify Start Errors:
3
: Invalid phone number format.9
: Partner quota exceeded (account balance issue).1
: Throttled (too many requests).
- Common Verify Check Errors:
16
: Wrong code entered.17
: Code submission mismatch (wrongrequest_id
).6
: Verification request expired or too many incorrect attempts.1
: Throttled.
- Refer to the Vonage Verify API Reference for a full list. Our code specifically handles statuses
16
and6
in the/verify-otp
route for better user feedback.
- Retry Mechanisms: The Vonage Verify API handles retries for sending the initial OTP (SMS -> TTS -> TTS). Your application doesn't need to retry sending. For checking the OTP, if you get a transient network error when calling
vonage.verify.check
, you could implement a simple retry (e.g., wait 1 second, try again once). However, for this basic example, we simply show an error. Retrying on specific Vonage status codes (like16
- wrong code) makes no sense; the user needs to try again.
6. Database Schema and Data Layer (Considerations)
This simple example uses an in-memory variable (verifyRequestId
) to store the request_id
between the request and verification steps. This is not suitable for production.
-
Why a Database/Cache is Needed:
- Concurrency: Multiple users will use the app simultaneously. A single global variable will be overwritten, breaking the flow for everyone except the last user.
- Persistence: If the server restarts, the in-memory variable is lost, invalidating ongoing verification attempts.
- Scalability: Cannot scale horizontally (run multiple instances of the app) with in-memory state.
-
Production Approach:
- User Session: Implement user sessions (e.g., using
express-session
with a persistent store like Redis or a database). - Store
request_id
: Whenvonage.verify.start
succeeds, store theresult.request_id
in the user's session data. - Retrieve
request_id
: In the/verify-otp
route, retrieve the expectedrequest_id
from the user's current session. - Verify: Call
vonage.verify.check
using the retrievedrequest_id
and the user-submittedotpCode
. - Clear
request_id
: Upon successful verification (or possibly expiry/failure), clear therequest_id
from the session.
- User Session: Implement user sessions (e.g., using
-
Database Schema (Example if storing directly, simplified): You might have a table like
VerificationRequests
:Column Type Notes id
UUID/Serial Primary Key user_id
FK (Users) Link to your user table (if applicable) session_id
VARCHAR Link to session ID (if not user-based) request_id
VARCHAR(128) The Vonage request_id
(Index this)phone_number
VARCHAR(20) The number being verified (optional) status
VARCHAR(20) 'PENDING', 'VERIFIED', 'FAILED', 'EXPIRED' created_at
TIMESTAMP Timestamp of creation expires_at
TIMESTAMP Calculated expiry time (e.g., created_at + 5 mins) You would query this table based on
session_id
oruser_id
to find the activerequest_id
.
For this guide's scope, we omit database integration, but remember it's essential for any real-world application.
7. Adding Security Features
Security is paramount, especially when dealing with authentication.
- Input Validation and Sanitization:
- Phone Number: While Vonage validates the number format, you can add server-side checks (e.g., using a library like
google-libphonenumber
) to ensure it looks like a valid E.164 number before sending it to Vonage. - OTP Code: Ensure the code is numeric and matches the expected length (typically 4 or 6 digits). Our EJS template uses
pattern="\d{4,6}"
, but server-side validation is still necessary. - Sanitization: Since we're using EJS and primarily rendering messages, the risk of Cross-Site Scripting (XSS) is lower than if we were directly inserting user input into HTML. However, always be mindful of reflecting user input. EJS escapes output by default (
<%= ... %>
), which helps prevent XSS.
- Phone Number: While Vonage validates the number format, you can add server-side checks (e.g., using a library like
- Rate Limiting: Protect against brute-force attacks on both requesting codes and verifying them.
- Use middleware like
express-rate-limit
. - Apply stricter limits to the
/verify-otp
endpoint (e.g., 5 attempts per request ID or per phone number within the 5-minute window). - Apply limits to
/request-otp
(e.g., 3 requests per phone number per hour) to prevent SMS Pumping fraud and unnecessary costs. - Example (
express-rate-limit
):const rateLimit = require('express-rate-limit'); const otpRequestLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour max: 5, // Limit each IP to 5 requests per windowMs message: 'Too many OTP requests from this IP, please try again after an hour' }); const verifyAttemptLimiter = rateLimit({ windowMs: 5 * 60 * 1000, // 5 minutes (aligns with typical OTP expiry) max: 10, // Limit each IP to 10 verification attempts per windowMs message: 'Too many verification attempts from this IP within the time window, please try again later.' // In production, must key off requestId or phone number (stored in session/DB) }); // Apply to routes app.post('/request-otp', otpRequestLimiter, async (req, res) => { /* ... */ }); app.post('/verify-otp', verifyAttemptLimiter, async (req, res) => { /* ... */ });
- Important Note on Rate Limiting: The example above uses IP-based rate limiting, which is a good first step but insufficient on its own in production. Malicious actors can easily cycle through IP addresses, and legitimate users behind the same NAT or proxy could be unfairly blocked. For the
/verify-otp
endpoint especially, you must implement limiting based on therequestId
or the associated user/session (retrieved from your database or session store as discussed in Section 6). Similarly,/request-otp
should ideally be limited per phone number or user account, not just IP.
- Use middleware like
- Secure Credential Handling: We're using
.env
and.gitignore
, which is standard practice. Ensure the production environment variables are managed securely (e.g., using platform secrets management). - HTTPS: Always use HTTPS in production to encrypt data in transit. Use a reverse proxy like Nginx or Caddy, or platform-level TLS termination (e.g., Heroku, AWS ELB).
- Session Management: Implement secure session management if storing
request_id
in sessions (use secure, HTTP-only cookies, regenerate session IDs on login).
8. Handling Special Cases
- International Phone Numbers: The Vonage Verify API expects phone numbers in E.164 format (e.g.,
+14155552671
or14155552671
). Ensure your frontend or backend formats the number correctly before sending it. Inform users about the required format. - Concurrent Requests: Vonage prevents sending multiple verification requests to the same number within a short timeframe (around 30 seconds). The API will return an error (often
status: '10'
). Your application should handle this gracefully, perhaps informing the user to wait or check their phone for an existing code. The Vonage Verify API manages the state, so generally, you just report the error.- Note: Our current code reports the
error_text
for non-zero statuses, which would cover this.
- Note: Our current code reports the
- Code Expiry: Codes typically expire in 5 minutes (configurable via
pin_expiry
). If a user tries to verify an expired code, Vonage returnsstatus: '6'
. Our code handles this by showing an appropriate error message and suggesting they request a new code. - User Mistakes: Users might enter the wrong number or the wrong code. The error handling covers wrong codes (
status: '16'
). For wrong numbers, Vonage might returnstatus: '3'
(invalid format) or the request might succeed but the SMS goes nowhere. Ensure clear UI instructions. - Vonage Service Issues: While rare, API outages can occur. The
try...catch
blocks handle network errors. Implement monitoring (Section 10) to detect broader issues.
9. Implementing Performance Optimizations
For this specific OTP flow, performance optimization is less critical than security and reliability.
- Caching: Caching the result of a verification is generally not done. Caching the status of a Vonage request isn't usually necessary as the API calls are quick. Caching user sessions (using Redis, Memcached) is crucial for scalability (as discussed in Section 6) but isn't a direct optimization of the OTP flow itself.
- Resource Usage: Node.js is generally efficient. Ensure you handle asynchronous operations correctly (
async/await
) to avoid blocking the event loop. - Database/Session Store: If using a database or cache for
request_id
storage, ensure it's indexed appropriately (e.g., index onsession_id
oruser_id
). - Load Testing: Use tools like
k6
,artillery
, orJMeter
to simulate concurrent users requesting and verifying codes. Monitor server CPU/memory usage and Vonage API response times under load. Focus on ensuring rate limits and session handling scale correctly.