code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Article

How to Build SMS OTP Verification with MessageBird & Node.js (2025 Guide)

Learn how to implement secure SMS-based two-factor authentication using MessageBird Verify API and Node.js. Complete tutorial with code examples, security best practices, and production-ready error handling.

MessageBird OTP Verification with Node.js & Express: Complete 2FA Tutorial

Two-factor authentication (2FA) adds a critical layer of security to user accounts by requiring a password plus a verification code sent to a trusted device. One-time passwords (OTPs) delivered via SMS remain one of the most accessible methods for implementing 2FA in web applications.

⚠️ Security Note: While SMS-based OTP is functional and widely deployed, OWASP security guidelines (2025) identify SMS OTP as less secure than alternative 2FA methods due to vulnerabilities including SIM swapping attacks, SS7 protocol exploits, phishing, and man-in-the-middle attacks. Leading organizations (Microsoft, Twitter) have migrated to more secure alternatives like TOTP authenticator apps, push notifications, or hardware tokens. SMS OTP remains acceptable for low-to-medium security applications but should be combined with additional security measures (rate limiting, device fingerprinting, anomaly detection) for higher-risk use cases. Source: OWASP Multifactor Authentication Cheat Sheet (accessed January 2025)

This guide walks you through building a functional OTP verification flow in a Node.js application using the Express framework and the MessageBird Verify API. You'll build a simple web application that requests a user's phone number, sends a verification code via MessageBird, and validates the code entered by the user.

Project Goals:

  • Build a Node.js Express application demonstrating SMS-based OTP verification.
  • Securely integrate with the MessageBird Verify API.
  • Implement a clear user flow for entering a phone number and verifying the OTP.
  • Provide robust error handling and outline basic security considerations.

Technologies Used:

  • Node.js: JavaScript runtime environment.
  • Express: Minimalist web framework for Node.js.
  • MessageBird Verify API: Service for sending and verifying OTPs via SMS or voice.
  • MessageBird Node.js SDK: Simplifies interaction with the MessageBird API.
  • Handlebars: Templating engine for rendering HTML views.
  • dotenv: Module to load environment variables from a .env file.
  • body-parser: Middleware to parse incoming request bodies. Note: As of Express 4.16.0+, body-parser functionality is built into Express via express.urlencoded() and express.json(). The standalone body-parser package is no longer required for basic use cases, though it remains available for advanced configurations. This tutorial uses body-parser for clarity but you can replace it with Express built-in methods. Source: Express body-parser middleware documentation (accessed January 2025)

System Architecture:

The flow involves these components:

  1. User's Browser: Interacts with the Express application, submitting the phone number and OTP.
  2. Node.js/Express Server:
    • Serves HTML pages (via Handlebars).
    • Receives user input (phone number, OTP).
    • Communicates with the MessageBird API via the SDK.
    • Handles the logic for initiating and verifying OTPs.
  3. MessageBird Verify API:
    • Generates a secure OTP.
    • Sends the OTP via SMS to the user's phone number.
    • Receives verification requests from the Express server and confirms if the provided OTP is correct for the specific verification attempt.

Prerequisites:

  • Node.js 14.x or higher and npm (or yarn) installed.
  • A MessageBird account with a Live API Key.
  • A text editor or IDE (like VS Code).
  • Basic understanding of Node.js, Express, and asynchronous JavaScript.

1. Setting Up Your Node.js Project

Create your project directory and initialize it.

  1. Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.

    bash
    mkdir node-messagebird-otp
    cd node-messagebird-otp
  2. Initialize npm: Initialize the project with npm. This creates a package.json file.

    bash
    npm init -y

    The -y flag accepts the default settings.

  3. Install Dependencies: Install Express for the web server, Handlebars for templating, the MessageBird SDK, dotenv for environment variables, and body-parser to handle form submissions.

    bash
    npm install express express-handlebars messagebird dotenv body-parser
  4. Create Project Structure: Set up a basic directory structure for clarity:

    node-messagebird-otp/ ├── node_modules/ ├── views/ │ ├── layouts/ │ │ └── main.handlebars │ ├── step1.handlebars │ ├── step2.handlebars │ └── step3.handlebars ├── .env ├── .gitignore ├── index.js └── package.json

    Create these directories manually or use terminal commands:

    bash
    mkdir -p views/layouts
    touch views/layouts/main.handlebars views/step1.handlebars views/step2.handlebars views/step3.handlebars
    touch .env .gitignore index.js
    • views/: Contains Handlebars templates.
    • views/layouts/: Contains the main HTML structure template.
    • .env: Stores sensitive information like API keys (will be created next).
    • .gitignore: Specifies files/directories Git should ignore (like node_modules and .env).
    • index.js: The main application file.
  5. Create .gitignore: Add the following lines to .gitignore to prevent committing sensitive files and dependencies:

    text
    # Dependencies
    node_modules/
    
    # Environment variables
    .env
    
    # Log files
    *.log
    npm-debug.log*
    yarn-debug.log*
    yarn-error.log*
    
    # Optional editor directories
    .idea/
    .vscode/

2. Configuring MessageBird API Authentication

The application needs your MessageBird API key to authenticate requests. Use dotenv to manage this securely.

  1. Obtain MessageBird API Key:

    • Log in to your MessageBird Dashboard.
    • Navigate to the Developers section in the left-hand sidebar.
    • Click on the API access (REST) tab.
    • If you don't have a key, click "Add access key." Create a Live key, not a test key. The Verify API generally requires a Live key for full functionality, unlike some other APIs that might work partially with Test keys.
    • Copy your Live API access key. Treat this key like a password – keep it secret!
  2. Create .env File: In the root of your project directory (node-messagebird-otp/), create a file named .env.

  3. Add API Key to .env: Add your copied Live API key to the .env file:

    dotenv
    # .env
    MESSAGEBIRD_API_KEY=YOUR_LIVE_API_KEY_HERE

    Important: Replace YOUR_LIVE_API_KEY_HERE with the actual key you copied from the MessageBird dashboard.

    • Purpose: The MESSAGEBIRD_API_KEY variable holds your secret key. dotenv loads this into process.env so your application can access it without hardcoding it in the source code.

3. Building the Express Application

Set up the basic Express server and configure Handlebars.

  1. Create index.js: Create the main application file, index.js, in the project root.

  2. Initial Setup (index.js): Add the following code to index.js to require dependencies and initialize the Express app, MessageBird SDK, and Handlebars:

    javascript
    // index.js
    
    // 1. Require Dependencies
    const express = require('express');
    const exphbs = require('express-handlebars');
    const bodyParser = require('body-parser');
    const dotenv = require('dotenv');
    
    // 2. Load Environment Variables
    dotenv.config(); // This loads the variables from .env into process.env
    
    // 3. Initialize MessageBird SDK
    // Ensure MESSAGEBIRD_API_KEY is set in your .env file
    if (!process.env.MESSAGEBIRD_API_KEY || process.env.MESSAGEBIRD_API_KEY === 'YOUR_LIVE_API_KEY_HERE') {
        console.error('Error: MESSAGEBIRD_API_KEY is not set or is still the placeholder in the .env file.');
        process.exit(1); // Exit if the API key is missing or not replaced
    }
    const messagebird = require('messagebird')(process.env.MESSAGEBIRD_API_KEY);
    
    // 4. Initialize Express App
    const app = express();
    const port = process.env.PORT || 8080; // Use port from env or default to 8080
    
    // 5. Configure Handlebars
    // Note: express-handlebars v6.0+ requires using .engine property
    // Older versions (v5.x) used: exphbs({ defaultLayout: 'main' })
    // Current syntax for v6.0+: exphbs.engine({ defaultLayout: 'main' })
    // Source: express-handlebars npm changelog (January 2025)
    app.engine('handlebars', exphbs.engine({
        defaultLayout: 'main'
    }));
    app.set('view engine', 'handlebars');
    // Specify the views directory (optional if it's named 'views')
    app.set('views', './views');
    
    // 6. Configure Body Parser Middleware
    // This is needed to parse data from HTML forms
    // Alternative for Express 4.16.0+: app.use(express.urlencoded({ extended: true }));
    app.use(bodyParser.urlencoded({ extended: true }));
    
    // 7. Define Routes (We will add these in the next section)
    // app.get('/', ...);
    // app.post('/step2', ...);
    // app.post('/step3', ...);
    
    // 8. Start the Server
    app.listen(port, () => {
        console.log(`Server listening on http://localhost:${port}`);
    });
    • Require necessary modules.
    • dotenv.config() loads the .env file.
    • Initialize the MessageBird SDK, passing the API key from process.env. A check ensures the key exists and isn't the placeholder value.
    • Set up Express and define the port (allowing override via environment variable).
    • Configure Handlebars as the view engine, specifying the default layout file.
    • Add body-parser middleware to access form data via req.body.
  3. Create Main Layout (views/layouts/main.handlebars): This file defines the basic HTML structure for all pages. Create views/layouts/main.handlebars with the following content:

    handlebars
    {{! views/layouts/main.handlebars }}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>MessageBird OTP Verification</title>
        <style>
            body { font-family: sans-serif; padding: 20px; }
            h1 { color: #333; }
            p { color: #555; }
            form { margin-top: 15px; }
            input[type="tel"], input[type="text"] {
                padding: 8px;
                margin-right: 5px;
                border: 1px solid #ccc;
                border-radius: 4px;
            }
            input[type="submit"] {
                padding: 8px 15px;
                background-color: #007bff;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
            }
            input[type="submit"]:hover { background-color: #0056b3; }
            .error { color: red; font-weight: bold; margin-bottom: 10px; }
        </style>
    </head>
    <body>
        <h1>MessageBird OTP Verification Example</h1>
        <hr>
        {{{body}}} {{! This is where specific view content will be injected }}
    </body>
    </html>
    • The {{{body}}} placeholder is where content from other Handlebars views (step1, step2, step3) will be rendered.

4. Implementing SMS OTP Verification Flow

Implement the core logic: requesting the phone number, sending the OTP, and verifying the OTP.

Step 1: Requesting the Phone Number

  1. Create View (views/step1.handlebars): This view presents a form for the user to enter their phone number. Create views/step1.handlebars:

    handlebars
    {{! views/step1.handlebars }}
    
    {{#if error}}
        <p class="error">{{error}}</p>
    {{/if}}
    
    <p>Enter your phone number in international format (e.g., +14155552671) to receive a verification code:</p>
    
    <form method="post" action="/step2">
        <label for="number">Phone Number:</label>
        <input type="tel" id="number" name="number" placeholder="+1234567890" required aria-label="Phone number in international format" />
        <input type="submit" value="Send Code" />
    </form>
    • {{#if error}}...{{/if}}: Conditionally displays an error message if passed from the server.
    • The form POSTs data to the /step2 route.
    • The input name="number" makes the value accessible as req.body.number on the server.
    • type="tel" hints to browsers (especially mobile) that this is a phone number field.

Step 2: Sending the Verification Code

When the user submits their phone number, call the MessageBird Verify API to send the OTP.

  1. Create View (views/step2.handlebars): This view asks the user to enter the code they received. It also includes a hidden field to pass the verification id to the next step. Create views/step2.handlebars:

    handlebars
    {{! views/step2.handlebars }}
    
    {{#if error}}
        <p class="error">{{error}}</p>
    {{/if}}
    
    <p>We sent a verification code to your phone!</p>
    <p>Enter the 6-digit code below. The code expires in 30 seconds.</p>
    
    <form method="post" action="/step3">
        {{! Hidden input to carry the verification ID }}
        <input type="hidden" name="id" value="{{id}}" />
    
        <label for="token">Verification Code:</label>
        <input type="text" id="token" name="token" pattern="\d{6}" title="Enter the 6-digit code" required aria-label="6-digit verification code" />
        <input type="submit" value="Verify Code" />
    </form>
    <p><a href="/">Request a new code</a></p>
    • name="id" and value="{{id}}": Passes the verification ID received from MessageBird.
    • name="token": The input for the user-entered OTP.
    • pattern="\d{6}": Basic HTML5 validation for a 6-digit code.
    • Added code expiry information and a link to request a new code.
  2. Create Route (index.js): Add the POST /step2 route in index.js to handle the form submission from step1:

    javascript
    // index.js - Add this route before app.listen()
    
    // Route to handle phone number submission and initiate verification
    app.post('/step2', (req, res) => {
        const number = req.body.number;
    
        // Basic validation (insufficient for production; consider libraries like 'google-libphonenumber' for robust E.164 validation - see Security section)
        if (!number || !/^\+[1-9]\d{1,14}$/.test(number)) {
             console.warn(`Invalid phone number format attempt: ${number}`);
             // Note: This regex is a basic check. Production apps should implement more robust E.164 validation
             // using libraries like google-libphonenumber (discussed further in the Security section)
             // to handle various international formats correctly.
             return res.render('step1', { error: 'Invalid phone number format. Use international format (e.g., +14155552671).' });
        }
    
        console.log(`Initiating verification for: ${number}`);
    
        // Call MessageBird Verify API
        messagebird.verify.create(number, {
            // originator: 'VerifyApp', // Sender ID (alpha/numeric, max 11 chars or phone number)
            // IMPORTANT: Originator ('From' field) requirements vary drastically by country.
            // Alphanumeric senders (like 'VerifyApp') may require pre-registration or may not work
            // in regions like the US/Canada. Using a purchased MessageBird virtual number is often more reliable.
            // Check MessageBird documentation for country-specific Sender ID rules before deploying.
            originator: process.env.MESSAGEBIRD_ORIGINATOR || 'MessageBird', // Use env var or default
            template: 'Your verification code is %token.', // %token is replaced by the actual OTP
            // type: 'sms', // Default is sms
            // tokenLength: 6, // Default is 6
            // timeout: 30 // Default is 30 seconds
        }, (err, response) => {
            if (err) {
                // Handle API errors
                console.error('MessageBird Verify API error:', err);
                let errorMessage = 'Failed to send verification code. Try again.';
                // Provide more specific feedback if possible, checking safely
                if (err.errors && err.errors.length > 0 && err.errors[0].description) {
                    errorMessage = err.errors[0].description;
                } else if (err.statusCode === 401) {
                    errorMessage = 'Authentication failed. Check your API key.'
                }
                return res.render('step1', {
                    error: errorMessage
                });
            }
    
            // Verification initiated successfully
            console.log('Verification response:', response); // Log the full response for debugging
            // Render step 2, passing the verification ID
            res.render('step2', {
                id: response.id // Pass the verification ID to the next view
            });
        });
    });
    • Extract the number from req.body.
    • Basic validation checks if the number looks like an international format. Production apps need more robust validation. A warning comment and prose highlight this limitation.
    • messagebird.verify.create(number, options, callback) is called.
      • number: The user's phone number.
      • options:
        • originator: The sender ID displayed on the user's phone. Regulations vary. Alphanumeric IDs might require registration or fail in certain countries (e.g., US/Canada). Using a purchased MessageBird number is often safer. Consult MessageBird's country-specific rules. An environment variable MESSAGEBIRD_ORIGINATOR is suggested for flexibility.
        • template: The SMS message body. %token is mandatory and will be replaced by the generated OTP.
        • timeout (optional): How long the code is valid (default 30 seconds).
      • callback(err, response): Handles the asynchronous response.
        • If err, log the error and re-render step1 with an error message extracted safely from err.errors[0].description or a generic message.
        • If successful (response), log the response and render step2, passing response.id to the view. This id uniquely identifies this verification attempt.

Step 3: Verifying the Code

Take the id and the user-entered token and ask MessageBird if they match.

  1. Create View (views/step3.handlebars): A simple success message. Create views/step3.handlebars:

    handlebars
    {{! views/step3.handlebars }}
    
    <h2>Verification Successful!</h2>
    <p>Your phone number has been successfully verified.</p>
    <p><a href="/">Start Over</a></p>
  2. Create Route (index.js): Add the POST /step3 route in index.js to handle the submission from step2:

    javascript
    // index.js - Add this route before app.listen()
    
    // Route to handle OTP submission and complete verification
    app.post('/step3', (req, res) => {
        const id = req.body.id; // Verification ID from hidden input
        const token = req.body.token; // OTP entered by the user
    
        if (!id || !token) {
             console.warn('Missing ID or Token in verification attempt.');
             // Avoid revealing which one is missing to the user
             return res.render('step2', {
                 error: 'Verification failed. Try again.',
                 id: id // Pass ID back if available
             });
        }
    
        console.log(`Verifying code for ID: ${id}, Token: ${token}`);
    
        // Call MessageBird Verify API to verify the token
        messagebird.verify.verify(id, token, (err, response) => {
            if (err) {
                // Verification failed (invalid token, expired, etc.)
                console.error('MessageBird Verify token error:', err);
                let errorMessage = 'Verification failed. Check the code and try again.';
                 // Provide specific API error if available, checking safely
                 if (err.errors && err.errors.length > 0 && err.errors[0].description) {
                    errorMessage = err.errors[0].description + ' Try sending a new code.';
                }
                // Re-render step 2 with error, passing the original ID back
                return res.render('step2', {
                    error: errorMessage,
                    id: id // Pass ID back so the form still works for retry (if applicable)
                });
            }
    
            // Verification successful!
            console.log('Verification successful:', response);
            // IMPORTANT: In a real app, you would now update the user's session/state
            // to indicate successful verification before rendering success.
            // This example just shows the success page directly.
            res.render('step3');
        });
    });
    • Get the id and token from req.body. Basic check added for their presence.
    • messagebird.verify.verify(id, token, callback) is called.
      • id: The unique ID from the verify.create response.
      • token: The OTP code entered by the user.
      • callback(err, response):
        • If err, verification failed (wrong code, expired, etc.). Log the error and re-render step2 with an error message (extracted safely) and the original id (so the hidden field remains populated).
        • If successful (response), the code was correct. Log success and render the step3 success view. A comment highlights that real apps need session updates here.

5. Testing Your OTP Application

Your basic OTP verification flow is complete!

  1. Save Files: Ensure all files (index.js, .env, .gitignore, and all .handlebars files in the views directories) are saved. Make sure you replaced the placeholder in .env.

  2. Start the Server: Open your terminal in the node-messagebird-otp directory and run:

    bash
    node index.js

    You should see: Server listening on http://localhost:8080. If you see an error about the API key, double-check your .env file.

  3. Test: Open your web browser and navigate to http://localhost:8080.

    • Enter your phone number in international format (e.g., +14155551234).
    • Click "Send Code."
    • Check your phone for an SMS containing the code (check spam if needed, and ensure your originator is valid).
    • Enter the code on the verification page.
    • Click "Verify Code."
    • You should see the "Verification Successful!" page. Try entering an incorrect code or waiting too long (over 30 seconds) to see the error handling.

Common Issues:

  • Port already in use: If port 8080 is busy, set PORT=3000 in your .env file and restart.
  • Module not found: Run npm install again to ensure all dependencies are installed.
  • API key errors: Verify your .env file contains the correct Live API key.

6. Error Handling and Logging Best Practices

Production applications require robust error handling strategies beyond basic console.log:

  • Logging: Use a dedicated logging library like Winston or Pino. Configure log levels (debug, info, warn, error) and output formats (JSON works well with log aggregation tools). Log critical events: verification initiation, API errors, successful verification, failed verification attempts.
  • MessageBird Errors: The err object from the MessageBird SDK often contains an errors array (e.g., err.errors[0]). Use err.errors[0].code for specific error types and err.errors[0].description (accessed safely, as shown) for user-friendly messages. Refer to MessageBird API documentation for error code meanings.
  • User Feedback: Provide clear, non-technical error messages to the user. Avoid exposing raw API errors directly. Guide the user on what to do next (e.g., "Invalid code, try again," "Code expired, request a new one").
  • Retry Mechanisms: For transient network errors when calling MessageBird, implement simple retry logic (e.g., retry once or twice with a short delay). For user input errors (invalid token), let the user resubmit the form on the /step2 page. If the code expires or too many attempts fail, guide the user back to / to request a new code.

Example Logging Enhancement (Conceptual):

Note: The following snippet is conceptual. Implementing this requires choosing and configuring a specific logging library (e.g., Winston, Pino) and integrating it into your application.

javascript
// Conceptual example using a placeholder logger
// const logger = require('./logger'); // Assume a configured logger exists

// Inside verify.create callback error handling:
// logger.error({
//     message: 'MessageBird Verify API error during creation',
//     apiError: err, // Log the full error object for detailed debugging
//     userNumber: number // Log relevant context (avoid logging sensitive data excessively)
// });
// return res.render('step1', { error: userFriendlyMessage });

// Inside verify.verify callback error handling:
// logger.warn({
//      message: 'MessageBird token verification failed',
//      verifyId: id,
//      apiError: err
// });
// return res.render('step2', { error: userFriendlyMessage, id: id });

7. Security Considerations for SMS Authentication

While MessageBird handles OTP generation and security, consider these points in your application:

  • Input Validation:
    • Phone Numbers: The basic regex ^\+[1-9]\d{1,14}$ is a start but insufficient for production. Use libraries like google-libphonenumber (npm install google-libphonenumber) for robust parsing and validation according to E.164 standards. Sanitize input to prevent potential injection issues. Learn more about phone number validation best practices.
    • OTP Token: Ensure the token format matches expectations (e.g., 6 digits). body-parser helps prevent basic payload manipulation, but always validate input types and lengths on the server.
  • Rate Limiting: Crucial to prevent abuse (repeatedly sending OTPs to a number, brute-forcing tokens). Implement rate limiting on:
    • The /step2 endpoint (requesting OTPs): Limit requests per phone number and/or IP address per time window (e.g., 1 request per minute, 5 requests per hour per number).
    • The /step3 endpoint (verifying OTPs): Limit verification attempts per verification ID and/or IP address per time window (e.g., 5 attempts per 15 minutes per ID).
    • Use middleware like express-rate-limit.
  • API Key Security: Never commit your .env file or hardcode the API key. Use environment variables managed securely in your deployment environment.
  • HTTPS: Always use HTTPS in production to encrypt communication between the user's browser and your server.
  • Session Management (Critical Gap in this Example): This guide demonstrates the OTP sending and verification mechanism in isolation. A real-world login or 2FA flow requires proper session management (e.g., using express-session with a secure store). After successful OTP verification (Step 3), update the user's server-side session state to grant access or mark the 2FA challenge as complete, rather than just showing a success page. The current example does not implement this critical session component.
  • Secure State Management: Passing the verification id via a hidden form field between steps is simple but relies on client-side state. For higher security, especially in complex flows, consider storing the verification attempt status (e.g., "pending," "verified") server-side, perhaps linked to the user's session or a short-lived database record, instead of solely relying on the client passing the id back correctly.
  • Brute Force Protection: Rate limiting on /step3 helps. You could also implement lockouts after too many failed attempts for a specific verification ID or user account (if integrated with user accounts).
  • CSRF Protection: Implement CSRF tokens using libraries like csurf to protect your forms from cross-site request forgery attacks.

Example Rate Limiting (Conceptual):

Note: The following snippet using express-rate-limit is conceptual. Install the library (npm install express-rate-limit) and integrate this middleware into your routes.

javascript
// Conceptual example using express-rate-limit
// const rateLimit = require('express-rate-limit');

// // Limit OTP requests per phone number
// const otpRequestLimiter = rateLimit({
//     windowMs: 15 * 60 * 1000, // 15 minutes
//     max: 5, // Limit each number to 5 requests per windowMs
//     message: 'Too many OTP requests from this number, try again after 15 minutes.',
//     keyGenerator: (req, res) => req.body.number, // Use phone number as key
//     handler: (req, res, next, options) => {
//          console.warn(`Rate limit exceeded for OTP request: ${req.body.number}`);
//          res.status(options.statusCode).render('step1', { error: options.message });
//     }
// });

// // Limit verification attempts per verification ID
// const otpVerifyLimiter = rateLimit({
//     windowMs: 10 * 60 * 1000, // 10 minutes
//     max: 10, // Limit each ID to 10 verification attempts per windowMs
//     message: 'Too many verification attempts, try again later or request a new code.',
//      keyGenerator: (req, res) => req.body.id, // Use verification ID as key
//      handler: (req, res, next, options) => {
//          console.warn(`Rate limit exceeded for OTP verification: ${req.body.id}`);
//          // Pass ID back so form still works potentially
//          res.status(options.statusCode).render('step2', { error: options.message, id: req.body.id });
//     }
// });

// app.post('/step2', otpRequestLimiter, (req, res) => { /* ... route logic ... */ });
// app.post('/step3', otpVerifyLimiter, (req, res) => { /* ... route logic ... */ });

8. Testing Strategies for 2FA Implementation

  • Manual Testing: Follow the steps in Section 5. Test edge cases:
    • Invalid phone number formats (non-international, too short/long).
    • Incorrect OTP codes.
    • Expired OTP codes (wait > 30 seconds before submitting).
    • Submitting the forms with empty fields.
    • Rapidly requesting codes or attempting verification (if rate limiting is implemented).
  • Automated Testing (Recommended for Production):
    • Unit Tests: Test individual functions or modules in isolation (e.g., phone number validation logic). Mock the MessageBird SDK calls using libraries like sinon or jest.mock.
    • Integration Tests: Test the interaction between different parts of your application, including routes. Use libraries like supertest to make HTTP requests to your running Express app and assert responses. You would still likely mock the actual MessageBird API calls to avoid sending real SMS messages and incurring costs during tests.

9. Deployment Considerations

  • Environment Variables: Ensure MESSAGEBIRD_API_KEY (and potentially MESSAGEBIRD_ORIGINATOR) is set as an environment variable in your deployment platform (Heroku Config Vars, AWS Secrets Manager, Docker environment variables, etc.). Do not deploy your .env file.

  • Platform: Choose a hosting platform (Heroku, AWS EC2/ECS/Lambda, Google Cloud Run, DigitalOcean App Platform, etc.). Follow their specific Node.js deployment guides.

  • package.json Scripts: Add a start script to your package.json:

    json
    // package.json
    "scripts": {
      "start": "node index.js",
      "test": "echo \"Error: no test specified\" && exit 1"
    },

    Most platforms use npm start to run your application.

  • HTTPS: Configure HTTPS on your deployment platform or via a load balancer/proxy.

  • CI/CD (Continuous Integration/Continuous Deployment): Set up a pipeline (GitHub Actions, GitLab CI, Jenkins) to automatically test and deploy your application when changes are pushed to your repository.

10. Troubleshooting Common Issues

  • Invalid API Key: Error messages like "Authentication failed" usually point to an incorrect or missing MESSAGEBIRD_API_KEY. Double-check the key in your environment variables/.env file and ensure it's a Live key and not the placeholder.
  • Invalid Phone Number: Errors like "recipient is invalid" mean the number format submitted to the API was incorrect. Ensure it's in international format (e.g., +1...). Use robust validation (Section 7).
  • SMS Not Received:
    • Check the MessageBird Dashboard logs (often under "Verify API Logs," "SMS Logs," or similar) for detailed delivery statuses and error messages directly from carriers. This is the most common way to diagnose delivery issues.
    • Verify the originator being used is valid for the destination country. Alphanumeric senders are restricted in some regions (like the US/Canada) and may require pre-registration. Try using a purchased MessageBird virtual number as the originator if allowed. Check MessageBird's documentation on sender IDs.
    • Ensure the destination phone has signal and isn't blocking messages from unknown senders or shortcodes.
    • Check for account balance issues on MessageBird. For more information about SMS pricing and delivery, see our MessageBird SMS pricing guide.
  • Token Verification Errors:
    • Verification code is incorrect: User entered the wrong code.
    • Verification has expired: User took too long (default > 30 seconds) to enter the code.
    • Verification not found or has already been verified: The id might be wrong, or the code was already used successfully.
  • Performance Issues:
    • If experiencing latency, check MessageBird service status and monitor API response times.
    • Implement timeout handling in your API calls to gracefully handle slow responses.
    • Use connection pooling if making multiple concurrent requests.

Looking to expand your SMS capabilities? Check out these related guides:

Frequently Asked Questions

How to implement 2FA with MessageBird in Node.js?

Implement 2FA by integrating the MessageBird Verify API into your Node.js/Express app. This involves collecting the user's phone number, sending an OTP via MessageBird's API, and then verifying the code entered by the user against MessageBird's system. Remember to handle errors, logging, and security aspects for a robust solution.

What is the MessageBird Verify API?

The MessageBird Verify API is a service that allows you to send and verify one-time passwords (OTPs) via SMS or voice calls. It handles OTP generation and security, simplifying the implementation of 2FA in your applications.

Why use MessageBird for OTP verification?

MessageBird provides a reliable and easy-to-use API and SDK for sending and verifying OTPs. It integrates seamlessly with Node.js and Express, offering a quick way to implement 2FA with built-in security features.

When should I add error handling for MessageBird OTP?

Error handling is essential from the start. Implement robust error handling for API calls, user input validation, and other potential issues like network problems. This is crucial for a production-ready 2FA system.

Can I customize the SMS message sent by MessageBird?

Yes, you can customize the SMS message template using the `template` option in the `messagebird.verify.create` method. Use the `%token` placeholder in the message, which MessageBird replaces with the generated OTP.

How to validate phone numbers for MessageBird API?

While a basic regex is provided, it's insufficient. Use a dedicated library like 'google-libphonenumber' to correctly parse and validate phone numbers in E.164 format for reliable international support.

What are the MessageBird API key security best practices?

Store your MessageBird API key as an environment variable, loaded using the 'dotenv' library. Never hardcode it in your source code or commit it to version control. Securely manage these credentials in your deployment environment.

How to set up the project for MessageBird OTP in Node.js?

Start by creating a project directory, initializing npm, installing required packages (express, express-handlebars, messagebird, dotenv, body-parser), and creating the necessary project file structure. Don't forget to set up your .gitignore file to keep your API keys secure.

How to handle rate limiting for OTP requests with MessageBird?

Implement rate limiting using middleware like 'express-rate-limit' for both `/step2` (OTP requests) and `/step3` (OTP verification). This limits requests per phone number/IP address within timeframes to prevent abuse.

What technologies are used in the Node.js Express OTP tutorial?

This tutorial uses Node.js, Express, the MessageBird Verify API and Node.js SDK, Handlebars, dotenv, and body-parser. It covers setting up the project, configuring the environment, building the Express application, and implementing the OTP flow securely.

Why does my MessageBird integration show "Authentication failed"?

This error typically means your MessageBird API key is incorrect or missing. Double-check your .env file and environment variables to ensure the correct Live API key is being used, not the placeholder.

How to fix "recipient is invalid" error with MessageBird Verify API?

This indicates an incorrect phone number format. Ensure the number is in the international E.164 format (e.g., +1...). Use 'google-libphonenumber' for robust validation to avoid this issue reliably.

When should I use a purchased MessageBird number as originator?

Alphanumeric originator IDs are often restricted, especially in regions like the US/Canada. Using a purchased MessageBird virtual number as the originator for your SMS messages is more reliable and often necessary for compliance.

How can I troubleshoot SMS messages not being received with MessageBird?

First, consult the MessageBird Dashboard logs for detailed delivery information from carriers. Verify your 'originator' ID's validity for the destination, check the recipient's phone settings and signal, and confirm your MessageBird account balance.

What are common MessageBird token verification errors and how to resolve them?

Common errors include incorrect or expired codes, or a code that was already used. Guide the user with specific messages (e.g., "invalid code," "code expired") and suggest appropriate actions like retrying or requesting a new code.