Frequently Asked Questions
Implement 2FA by using Fastify, the MessageBird Verify API, and the MessageBird Node.js SDK. This combination allows you to create a secure OTP flow, sending codes via SMS and verifying user input for enhanced login security.
The MessageBird Verify API simplifies OTP generation, delivery via SMS, and code verification. It handles the complexities of sending and validating OTPs, allowing developers to focus on application logic.
2FA adds a second authentication factor, typically a user's mobile phone, making it significantly harder for attackers to gain access even with a compromised password. This protects against account takeovers.
Use SMS-based OTP 2FA when you need to strengthen user authentication beyond just username/password logins. This is especially important for sensitive applications or where regulatory compliance mandates stronger security.
Yes, you can customize the SMS message template using the template parameter in the verify.create call. The %token placeholder will be replaced with the generated OTP. Be mindful of character limits and any country-specific restrictions.
Use the messagebird.verify.create method with the user's phone number and desired parameters like the message template and sender ID. This initiates the OTP generation and SMS delivery process.
Fastify is a high-performance Node.js web framework used to build the backend service that handles user interaction, API calls to MessageBird, and rendering HTML pages.
Call the messagebird.verify.verify method with the verification ID (received from verify.create) and the user-submitted OTP code. This validates the code against MessageBird's records.
You will need Node.js and npm installed, a MessageBird account with a live API key, a mobile phone for testing, and a basic understanding of Node.js, JavaScript, and web concepts.
This project utilizes Node.js with Fastify, the MessageBird Verify API and Node.js SDK, Handlebars for templating, and dotenv for environment variables, providing a comprehensive solution for SMS OTP 2FA.
Use npm install fastify @fastify/view handlebars @fastify/formbody dotenv messagebird to install all the necessary dependencies for the Fastify server, templating, environment variables and the MessageBird SDK.
Proper error handling, including logging and user-friendly feedback, is crucial for a good user experience and to prevent issues like account lockouts if the OTP process encounters problems.
Store phone numbers in E.164 format (e.g. +14155552671). This format ensures consistency and improves reliability when integrating with services like MessageBird, and is recommended by the article for reliable validation.
Rate limiting restricts the number of OTP requests from a user or IP address within a given time window. It's essential to prevent abuse, such as SMS bombing or brute-force attacks, protecting both your MessageBird account and your users.
Avoid passing the verification ID via a hidden form field. Instead, use server-side sessions to securely store and retrieve the ID between requests, preventing potential tampering.
Build Secure OTP Two-Factor Authentication with MessageBird, Fastify, and Node.js
Build a secure MessageBird OTP (One-Time Password) Two-Factor Authentication flow in Node.js using Fastify and the MessageBird Verify API for SMS delivery. This guide shows you how to create a Fastify 2FA application that sends OTP codes via SMS and verifies user input, adding a crucial security layer to your Node.js SMS authentication process.
Two-factor authentication enhances your application security beyond traditional username/password logins. It protects against account takeovers from compromised passwords by requiring users to possess a secondary factor – typically their mobile phone – to verify their identity. You'll use MessageBird's Verify API, which simplifies OTP generation, delivery, and verification. Fastify provides the high-performance, low-overhead web framework for building your backend service efficiently.
Prerequisites
Before you begin, ensure you have:
Cost Warning: This tutorial sends real SMS messages, which incur charges based on MessageBird's pricing (typically $0.01–$0.10 per message depending on destination country). Monitor your usage in the MessageBird dashboard to avoid unexpected charges.
1. Set Up Your Node.js Fastify Project
Initialize your Node.js project and install the necessary dependencies.
Create Your Project Directory:
Open your terminal and create a new directory for your project, then navigate into it.
Initialize Your Node.js Project:
Create a
package.jsonfile to manage your project dependencies and scripts.Install Your Dependencies:
Install Fastify, the MessageBird SDK, templating engine, form body parser, and dotenv.
fastify: The core web framework providing high-performance routing and plugin system.@fastify/view: Plugin for rendering templates.handlebars: The templating engine for HTML views.@fastify/formbody: Plugin to parseapplication/x-www-form-urlencodedrequest bodies.dotenv: Loads environment variables from a.envfile.messagebird: The official Node.js SDK for the MessageBird API.Create Your Project Structure:
Set up a basic directory structure for clarity.
views/: Contains your Handlebars template files.views/layouts/: Contains your layout templates (like headers/footers).server.js: Your main application file where you configure and run the Fastify server..env: Stores sensitive information like API keys (never commit this to version control)..env.example: An example file showing required environment variables (safe to commit).step*.hbs: Template files for each step of your OTP flow.main.hbs: Your main layout template..gitignore: Specifies files that Git should ignore.Configure Your Environment Variables:
live_) for this tutorial, as test keys won't send actual SMS messages..envfiles:.env.exampleand add this line:.envand add your actual live API key:.envto your.gitignorefile to prevent accidentally committing your secret key. Open.gitignoreand add:Set Up Your Basic Fastify Server:
Open
server.jsand add the initial server configuration:We initialize
dotenvfirst to load environment variables. We initialize Fastify with logging enabled (logger: true), which uses Pino for high-performance structured logging. We register@fastify/formbodyto handle HTML form submissions and@fastify/viewto render Handlebars templates. We initialize the MessageBird SDK using the API key from the environment variable, with a check to ensure the key exists. A basicstartfunction handles server listening and error logging.2. Implement Core Functionality (OTP Flow) & 3. Build the API Layer
Build the routes and logic for your OTP flow. Fastify routes handle both core functionality and the API layer.
Step 1: Request Phone Number
Create Your Layout (
views/layouts/main.hbs):This file provides the basic HTML structure for all your pages.
Create Your Phone Number Entry Page (
views/step1.hbs):This template displays the form for entering the phone number.
It includes a conditional block
{{#if error}}to display error messages passed from the server. The formPOSTs data to the/send-otproute. Input typetelhelps mobile browsers display numeric keypads. Thearia-labelattribute improves accessibility for screen readers.E.164 Format Requirements: E.164 is the international phone number format that starts with
+followed by country code (1–3 digits) and subscriber number (up to 15 total digits). Examples: US (+1 415 555 2671), UK (+44 20 7946 0958), Germany (+49 30 12345678).Create the Route to Display Your Form (
server.js):Add this route handler before the
start()function call.Step 2: Send Verification Code
Create Your OTP Entry Page (
views/step2.hbs):This template displays the form for entering the received OTP.
Includes an error display block and a hidden input field
name="id"that stores theverification IDreceived from MessageBird, which is crucial for the verification step. Security Note: Passing the ID via a hidden field is simple but less secure. For production, store this ID in a server-side session (see Section 7). The formPOSTs data to the/check-otproute. We've added a link to resend the OTP if the user doesn't receive it.Create Your Route to Handle Sending OTP (
server.js):Add this route handler.
It retrieves the
numberfrom the parsed form body (request.body) and validates the E.164 format using the regex/^\+[1-9]\d{1,14}$/. Important: For production, use a dedicated library likegoogle-libphonenumberfor reliable international number validation. Parameters forverify.create:originator: The name/number displayed as the sender on the SMS. Note restrictions in some countries (like the US where alphanumeric sender IDs may not work).template: The message text where%tokenis replaced by MessageBird with the generated OTP.type: Set tosmsfor text messages. Usettsfor voice calls, oremailfor email verification.timeout: Valid range is 30 to 172,801 seconds (up to 2 days) per MessageBird API specification. Default: 30 seconds.tokenLength: Valid range is 6 to 10 characters per MessageBird API specification. Default: 6.maxAttempts: Valid range is 1 to 10 attempts per MessageBird API specification. Controls how many failed verification attempts are allowed before the Verify object is marked as failed. Default: 1.datacoding: Options are 'plain' (GSM 03.38 characters only), 'unicode' (for non-GSM characters like emoji or non-Latin scripts), or 'auto' (MessageBird auto-detects) per MessageBird API specification. Default: 'plain'.We wrap the callback-based
messagebird.verify.createin aPromisefor cleanerasync/awaitsyntax. On success, renderstep2.hbs, passing theresponse.id(the verification ID) to the template. On error, log the error and re-renderstep1.hbswith an appropriate error message.Common MessageBird API Error Codes:
SMS Delivery Time: Most SMS messages deliver within 2–10 seconds. Network congestion or international routing can cause delays up to 60 seconds. The default 30-second timeout accommodates most scenarios.
Step 3: Verify the Code
Create Your Success Page (
views/step3.hbs):Display this template upon successful verification.
Create Your Route to Handle OTP Verification (
server.js):Add this final route handler.
Retrieve the
idandtokenfrom the form body and validate the token format using/^\d{6}$/. Handle the missingidcase by redirecting. Wrapmessagebird.verify.verifyin aPromiseand callverifyTokenwith theidandtoken. On success, log the result and renderstep3.hbs. At this point in a real application, mark the user/phone number as verified in your database. On error (e.g., incorrect token, expired token), log the error and re-renderstep2.hbswith theidand an error message.Brute Force Protection: Implement rate limiting (see Section 7) to prevent attackers from guessing OTP codes. MessageBird's
maxAttemptsparameter limits failed verification attempts per verification ID (1–10 attempts, default 1). After reaching the limit, the verification object is marked as failed and cannot be used again.Token Expiration: The default 30-second timeout balances security and usability. Users receive the SMS quickly (2–10 seconds), leaving 20+ seconds to enter the code. For better UX, consider extending to 60–120 seconds if your user base includes less tech-savvy users.
4. Integrate with MessageBird Verify API
This section focuses on MessageBird integration details.
MESSAGEBIRD_API_KEY.live_) for actual SMS delivery. Test keys (start withtest_) will not send real SMS messages and are only for API testing..envfile, which is loaded bydotenv. This file must not be committed to version control (ensure it's in.gitignore).server.js:type: 'tts') after a failed SMS attempt.MESSAGEBIRD_API_KEY:live_followed by alphanumeric characters.API Key Rotation Best Practices:
MessageBird API Rate Limits:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset5. Implement Error Handling for MessageBird OTP Verification
try...catchblocks aroundasyncoperations, especially API calls.errobject in MessageBird SDK callbacks (or the rejected Promise in wrapped functions).err.errors[0].descriptionwhen available from MessageBird API errors.errorvariable passed to the template.fastify.log) is enabled (logger: true). Use levels likeinfo,warn,error.fastify.log.error('MessageBird Verify Create error:', err);verify.createis generally not idempotent if called with the same number; it will likely initiate a new OTP. Retryingverify.createcould result in multiple SMS messages.async-retry. Don't retry validation errors (4xx).Common Error Scenarios and Solutions:
google-libphonenumberlibrarytimeoutparameter or implement resend functionalitymaxAttemptsCircuit Breaker Pattern for Production: Implement a circuit breaker to prevent cascading failures when MessageBird API is unavailable. Use libraries like
opossum:Monitoring and Alerting:
fastify-metricsfor Prometheus integration6. Creating a Database Schema and Data Layer
This specific example focuses purely on the OTP flow and doesn't require a database. However, in a real-world application integrating this flow, you would typically need a database to:
is_phone_verified) on the user record.Conceptual Schema (PostgreSQL):
Note:
gen_random_uuid()is specific to PostgreSQL and may require enabling thepgcryptoextension. Other databases have different functions for generating UUIDs (e.g.,UUID()in MySQL) or you might generate UUIDs in your application code.Data Layer Implementation (Conceptual using
pglibrary):node-pg-migrateor ORM-specific migration tools (e.g., Prisma Migrate, Sequelize CLI) to manage schema changes reliably across environments.Connection Pooling Configuration:
7. Adding Security Features
Beyond the core 2FA logic, several security measures are essential for production.
Input Validation:
/^\+[1-9]\d{1,14}$/). Use libraries likegoogle-libphonenumberfor robust validation. Sanitize input to prevent potential injection issues./^\d{6}$/).XSS Protection: Handlebars automatically escapes HTML by default, preventing XSS attacks. Use
{{{ }}}only for trusted content.CSRF Protection: For production, implement CSRF tokens using
@fastify/csrf-protection:Rate Limiting:
Crucial to prevent SMS pumping abuse and brute-force attacks.
@fastify/rate-limit:npm install @fastify/rate-limitserver.js:/send-otp,/check-otp) if needed, or globally as shown above. AdjustmaxandtimeWindowbased on expected usage patterns and risk tolerance.Choosing Appropriate Rate Limits:
/send-otp: 3–5 attempts per phone number per hour (prevents SMS pumping)/check-otp: 5–10 attempts per verification ID total (MessageBird'smaxAttemptsprovides additional protection)Distributed Rate Limiting for Multi-Server Deployments: Use Redis as a shared store:
Secure Session Management:
verification IDvia a hidden form field is insecure.verification IDand potentially the associated phone number between the/send-otpand/check-otprequests.@fastify/sessionand a store like@fastify/cookie:npm install @fastify/session @fastify/cookieserver.js:SESSION_SECRETenvironment variable (at least 32 characters). Secure cookie settings (secure: truefor HTTPS,httpOnly: true,sameSite: 'strict'). Consider session expiration and persistent stores for production.Session Fixation Attack Prevention: Regenerate session IDs after successful authentication:
Redis Session Store Configuration:
HTTPS:
Always use HTTPS in production to encrypt communication between the client and server, protecting sensitive data like phone numbers, OTP codes, and session cookies. Configure your deployment environment (e.g., using a reverse proxy like Nginx or Caddy, or platform services like Heroku, AWS ELB) to handle TLS termination.
TLS Configuration Recommendations:
ECDHE-RSA-AES128-GCM-SHA256,ECDHE-RSA-AES256-GCM-SHA384Strict-Transport-Security: max-age=31536000; includeSubDomainsDependency Security:
Regularly audit dependencies for known vulnerabilities using tools like
npm auditor Snyk, and keep them updated.Automated Dependency Updates: Use Dependabot (GitHub) or Renovate to automatically create PRs for dependency updates.
MessageBird Security:
Webhook Signature Verification: If implementing delivery status callbacks, verify webhook signatures:
Frequently Asked Questions
How do I implement OTP 2FA in Node.js?
Implement OTP 2FA in Node.js by using the MessageBird Verify API with Fastify. Install the
messagebirdSDK, create routes for sending OTP codes via/send-otp, and verify user-submitted tokens via/check-otp. Usemessagebird.verify.create()to generate and send OTP codes, thenmessagebird.verify.verify()to validate user input.What is the MessageBird Verify API?
The MessageBird Verify API handles OTP generation, SMS delivery, and token verification for two-factor authentication. It automatically generates random verification codes (6–10 digits), sends them via SMS, email, or voice call, and validates user-submitted tokens within configurable timeout windows (30 seconds to 2 days).
How do I use Fastify for 2FA?
Use Fastify for 2FA by registering the
@fastify/formbodyplugin to parse form submissions and@fastify/viewfor template rendering. Create POST routes that integrate with MessageBird's Verify API to send and verify OTP codes. Fastify's high-performance architecture (v5 current) handles authentication flows efficiently with built-in logging via Pino.What Node.js version does MessageBird SDK require?
MessageBird Node.js SDK requires Node.js >= 0.10 but we recommend Node.js 18.0 (LTS) or higher for production applications. The SDK supports both CommonJS (
require('messagebird')) and ES6 module syntax (import { initClient } from 'messagebird'), making it compatible with modern Node.js applications and TypeScript projects.How long do MessageBird OTP codes remain valid?
MessageBird OTP codes remain valid for 30 seconds by default. You can configure the timeout parameter from 30 to 172,801 seconds (up to 2 days) when calling
verify.create(). Set longer timeouts for email-based OTP or shorter windows (30–60 seconds) for SMS to balance security and usability.Can I customize MessageBird OTP token length?
Yes, set the
tokenLengthparameter when callingverify.create(). Valid range is 6–10 characters per MessageBird API specification. Shorter codes (6 digits) work better for SMS due to message length constraints, while longer codes (8–10 digits) provide higher entropy for sensitive operations.How do I secure MessageBird API keys in production?
Store MessageBird API keys in environment variables via
.envfiles (never commit to version control). Use live keys (starting withlive_) for production and test keys (test_) for development. Add IP whitelisting in the MessageBird dashboard if your server uses static IPs, and monitor usage for anomalies via the dashboard.What's the difference between Fastify and Express for 2FA?
Fastify (v5 current) offers 6x faster performance than Express with built-in schema validation via JSON Schema and a robust plugin ecosystem. For 2FA flows, Fastify's native async/await support and low overhead make it ideal for high-throughput authentication services, while Express requires additional middleware for comparable functionality.
What should I do if users don't receive SMS messages?
If users don't receive SMS messages, check these common issues: verify the phone number is in valid E.164 format, ensure your MessageBird account has sufficient balance, confirm the destination country is supported (check MessageBird dashboard), review carrier filtering rules, and implement a fallback to voice OTP (
type: 'tts') or email verification. Network delays can take up to 60 seconds in some regions.How do I handle international SMS delivery?
International SMS delivery requires country-specific considerations: verify MessageBird supports the destination country (200+ countries available), be aware of sender ID restrictions (many countries don't support alphanumeric sender IDs), adjust timeout values for regions with slower carrier networks (60–120 seconds), check local compliance requirements (some countries require pre-registration), and monitor delivery rates per country in your MessageBird dashboard.