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.
This guide provides a step-by-step walkthrough for building a secure One-Time Password (OTP) Two-Factor Authentication (2FA) flow in a Node.js application using the Fastify framework and the MessageBird Verify API for SMS delivery. By the end of this tutorial, you will have a functional application that can send OTP codes via SMS and verify user input, adding a crucial layer of security to your user authentication process.
Implementing 2FA significantly enhances application security beyond traditional username/password logins. It protects against account takeovers resulting from compromised passwords by requiring users to possess a secondary factor – typically their mobile phone – to verify their identity. We'll use MessageBird's robust Verify API, which simplifies OTP generation, delivery, and verification. Fastify provides a high-performance, low-overhead web framework for building the backend service efficiently.
Project Overview and Goals
What We'll Build:
A simple Node.js web application using Fastify that demonstrates a complete SMS-based OTP 2FA flow:
Problem Solved:
This implementation addresses the need for stronger user authentication by adding a second factor (SMS OTP) to the login or verification process, mitigating risks associated with password-only security.
Technologies Used:
messagebird
Node.js SDK: Simplifies interaction with the MessageBird API.@fastify/view
&handlebars
: For server-side template rendering (HTML pages).@fastify/formbody
: To parse URL-encoded form submissions.dotenv
: To manage environment variables securely.System Architecture:
Prerequisites:
Final Outcome:
A functional Node.js application demonstrating SMS OTP 2FA, ready to be integrated into a larger authentication system.
1. Setting up the project
Let's initialize the Node.js project and install the necessary dependencies.
Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.
Initialize Node.js Project: Create a
package.json
file to manage project dependencies and scripts.Install Dependencies: Install Fastify, the MessageBird SDK, templating engine, form body parser, and dotenv.
fastify
: The core web framework.@fastify/view
: Plugin for rendering templates.handlebars
: The templating engine we'll use.@fastify/formbody
: Plugin to parseapplication/x-www-form-urlencoded
request bodies.dotenv
: Loads environment variables from a.env
file.messagebird
: The official Node.js SDK for the MessageBird API.Create Project Structure: Set up a basic directory structure for clarity.
views/
: Contains Handlebars template files.views/layouts/
: Contains layout templates (like headers/footers).server.js
: The main application file where the Fastify server is configured and run..env
: Stores sensitive information like API keys (this file should not be committed to version control)..env.example
: An example file showing required environment variables (safe to commit).step*.hbs
: Template files for each step of the OTP flow.main.hbs
: The main layout template..gitignore
: Specifies intentionally untracked files that Git should ignore.Configure Environment Variables:
.env
files:.env.example
and add the following line:.env
and add your actual live API key:.env
to your.gitignore
file to prevent accidentally committing your secret key. Open.gitignore
and add:Basic Fastify Server Setup: Open
server.js
and add the initial server configuration:dotenv
first to load environment variables.logger: true
).@fastify/formbody
to handle HTML form submissions.@fastify/view
and configure it to use Handlebars, pointing to ourviews
directory and specifying the default layout.start
function handles server listening and error logging.2. Implementing Core Functionality (OTP Flow) & 3. Building the API Layer
Now, let's build the routes and logic for the OTP flow. We'll combine core functionality and the API layer as Fastify routes handle both.
Step 1: Request Phone Number
Create Layout (
views/layouts/main.hbs
): This file provides the basic HTML structure for all pages.Create Phone Number Entry Page (
views/step1.hbs
): This template displays the form for entering the phone number.{{#if error}}
to display error messages passed from the server.POST
s data to the/send-otp
route.tel
helps mobile browsers display numeric keypads.Create Route to Display the Form (
server.js
): Add this route handler before thestart()
function call.Step 2: Send Verification Code
Create OTP Entry Page (
views/step2.hbs
): This template displays the form for entering the received OTP.name=""id""
stores theverification ID
received from MessageBird, which is crucial for the verification step. Security Note: Passing the ID via a hidden field is simple but less secure. A user could potentially tamper with it. For production, storing this ID in a server-side session (see Section 7) is strongly recommended.POST
s data to the/check-otp
route.Create Route to Handle Sending OTP (
server.js
): Add this route handler.number
from the parsed form body (request.body
)./^\+[1-9]\d{1,14}$/
. Important: For production, use a dedicated library likegoogle-libphonenumber
for more reliable international number validation.verify.create
call:originator
: The name/number displayed as the sender on the SMS. Note restrictions in some countries (like the US).template
: The message text.%token
is replaced by MessageBird with the generated OTP.type
: Set tosms
. Usetts
for voice calls.timeout
andtokenLength
can be added.messagebird.verify.create
in aPromise
for cleanerasync/await
syntax. A comment notes why this is done and suggests checking for native Promise support in the SDK.step2.hbs
, passing theresponse.id
(the verification ID) to the template. A security note reinforces the recommendation for using sessions instead of a hidden field.step1.hbs
with an appropriate error message, potentially using the description from the MessageBird error response.Step 3: Verify the Code
Create Success Page (
views/step3.hbs
): This template is shown upon successful verification.Create Route to Handle OTP Verification (
server.js
): Add this final route handler.id
andtoken
from the form body./^\d{6}$/
. Handles the missingid
case by redirecting, noting this limitation without sessions.messagebird.verify.verify
in aPromise
.verifyToken
with theid
andtoken
.step3.hbs
success page. At this point in a real application, you would typically mark the user/phone number as verified in your database.step2.hbs
, passing back theid
and an appropriate error message derived from the MessageBird response.4. Integrating with necessary third-party services
This section focuses specifically on the MessageBird integration details covered during setup.
MESSAGEBIRD_API_KEY
..env
file, 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.5. Implementing proper error handling, logging, and retry mechanisms
try...catch
blocks aroundasync
operations, especially API calls.err
object in MessageBird SDK callbacks (or the rejected Promise in our wrapped functions).err.errors[0].description
when available from MessageBird API errors.error
variable passed to the template.fastify.log
) is enabled (logger: true
). Use levels likeinfo
,warn
,error
.fastify.log.error('MessageBird Verify Create error:', err);
verify.create
is generally not idempotent if called with the same number; it will likely initiate a new OTP. Retryingverify.create
could result in multiple SMS messages.async-retry
. Avoid retrying validation errors (4xx).npm install async-retry
.verify.verify
might be safer but could lock out users if retried too aggressively with wrong codes. Rate limiting (Section 7) is generally a better approach here.6. 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 (e.g._ PostgreSQL):
Note:
gen_random_uuid()
is specific to PostgreSQL and may require enabling thepgcrypto
extension. 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
pg
library):node-pg-migrate
or ORM-specific migration tools (e.g., Prisma Migrate, Sequelize CLI) to manage schema changes reliably across environments.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-libphonenumber
for robust validation. Sanitize input to prevent potential injection issues (less common with phone numbers but good practice)./^\d{6}$/
).Rate Limiting: Crucial to prevent SMS pumping abuse and brute-force attacks.
@fastify/rate-limit
:npm install @fastify/rate-limit
server.js
:/send-otp
,/check-otp
) if needed, or globally as shown above. Adjustmax
andtimeWindow
based on expected usage patterns and risk tolerance. Implementing akeyGenerator
based on phone number or verification ID (requires session state or careful handling) is more effective than IP-based limiting alone.Secure Session Management:
verification ID
via a hidden form field is insecure.verification ID
and potentially the associated phone number between the/send-otp
and/check-otp
requests.@fastify/session
and a store like@fastify/cookie
:npm install @fastify/session @fastify/cookie
server.js
:SESSION_SECRET
environment variable. Secure cookie settings (secure: true
for HTTPS,httpOnly: true
). Consider session expiration and persistent stores for production.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.
Dependency Security: Regularly audit dependencies for known vulnerabilities using tools like
npm audit
or Snyk, and keep them updated.MessageBird Security: