Frequently Asked Questions
The Fastify API sends an OTP by making a request to the Infobip 2FA API. Infobip then delivers the SMS message containing the OTP to the user's phone number. The process involves sending data such as the application ID, message ID, recipient's phone number, and optionally the message language to the Infobip API from your Fastify backend.
Infobip acts as the communication platform, handling the actual sending and verification of SMS messages. It provides the necessary APIs to generate OTPs, deliver them via SMS, and verify user-submitted codes. Essentially, it bridges your application and the user's mobile device for OTP delivery.
Fastify is a high-performance Node.js web framework known for its speed and extensibility. These features make it well-suited for building efficient and scalable OTP verification systems. It offers built-in validation, logging, and other developer-friendly tools.
You'll need an API key, Base URL, Application ID, and Message ID from your Infobip account. Create a `.env` file (and a `.env.example` for version control without sensitive data) and store these credentials there. The Application ID and Message ID are obtained by creating a 2FA Application and a Message Template within Infobip's portal.
Use `npm install` to install necessary packages like `fastify`, `typescript`, `@types/node`, `ts-node-dev`, `@fastify/env`, `@fastify/sensible`, `@prisma/client`, `axios`, `prisma`, `@sinclair/typebox`, and `@fastify/type-provider-typebox`. These cover the core framework, TypeScript, environment variables, database interaction, and HTTP requests.
PostgreSQL is the recommended database, though Prisma supports others. You initialize Prisma using `npx prisma init --datasource-provider postgresql` which creates the `schema.prisma` file. This file defines your database models, including one for storing OTP attempts.
The `otp.service.ts` file contains functions for sending and verifying OTPs. The `sendOtp` function interacts with the Infobip API and stores the `pinId`. The `verifyOtp` function checks the user-submitted PIN against the stored `pinId` using the Infobip API and removes the OTP record from the database upon successful verification.
OTP records should be removed after successful verification. Additionally, expired or invalid OTP attempts should be cleaned up proactively during verification attempts or through scheduled cleanup tasks. This enhances security and prevents unnecessary database growth.
The architecture involves the End User (browser or app), the Fastify API (Node.js), the Infobip API (2FA service), the User's Phone, and a PostgreSQL database. The flow starts with the user initiating an OTP request, which goes through Fastify to Infobip, then to the user's phone. The user submits the received OTP to Fastify, which verifies it against Infobip, and grants access upon confirmation.
Yes, you can create custom message templates through the Infobip portal when setting up your 2FA Application. The templates can include dynamic placeholders like {{pin}} to inject the actual OTP code into the message text.
While not explicitly implemented in detail in the article, using libraries like `@fastify/rate-limit` allows you to set limits on how often users can attempt OTP verification, which mitigates brute-force attacks.
The `verifyOtp` function in the service checks for expired attempts and handles them accordingly, preventing reuse. The Infobip API returns appropriate codes that you can use to customize error responses to the user (e.g., expired or invalid PIN).
Prisma simplifies database access and management by providing a type-safe and efficient way to interact with your PostgreSQL database. It handles schema migrations, data modeling, and query building.
You need Node.js v18 or later, npm/yarn, an active Infobip account, access to a PostgreSQL database, and a basic understanding of TypeScript, Node.js, REST APIs, and asynchronous programming.
Use the `npm run dev` command to start the server in development mode. This uses `ts-node-dev` to run the TypeScript code directly with automatic reloading on file changes.
Implementing SMS OTP Verification with Fastify and Infobip
This guide provides a step-by-step walkthrough for building a production-ready SMS-based One-Time Password (OTP) or Two-Factor Authentication (2FA) system using Node.js, the Fastify framework, and the Infobip API. We will cover everything from initial project setup to deployment considerations and troubleshooting.
By following this guide, you will create a secure and reliable mechanism to verify user phone numbers via SMS, enhancing application security during registration, login, or critical actions.
Project Overview and Goals
What We're Building:
We will build a backend API using Fastify that exposes endpoints for:
Problem Solved:
This implementation addresses the need for robust user verification, mitigating risks like account takeover, fake registrations, and unauthorized access by confirming possession of a specific phone number.
Technologies Used:
System Architecture:
Prerequisites:
Final Outcome:
A Fastify application with API endpoints capable of sending and verifying SMS OTPs using Infobip, including basic security measures and database integration.
1. Setting up the Project
Let's initialize our Fastify project with TypeScript, Prisma, and necessary dependencies.
1.1 Initialize Project & Install Core Dependencies:
Open your terminal and run the following commands:
Explanation:
npm init -y
: Creates a basicpackage.json
.fastify
: The core framework.typescript
,@types/node
: TypeScript compiler and Node.js types.ts-node-dev
: Utility for running TypeScript projects with auto-reloading during development.@fastify/env
: Plugin for loading and validating environment variables.@fastify/sensible
: Adds useful defaults like error handling and headers.tsc --init ...
: Creates atsconfig.json
file configured for our project structure (src
for source,dist
for compiled output).1.2 Configure
package.json
Scripts:Update the
scripts
section in yourpackage.json
:Explanation:
build
: Compiles TypeScript code to JavaScript in thedist
directory.start
: Runs the compiled JavaScript application (for production).dev
: Runs the application usingts-node-dev
for development, automatically restarting on file changes.test
: Placeholder for running automated tests (not implemented in detail in this guide).1.3 Project Structure:
Create the following directories and files:
Explanation:
plugins
: Encapsulates Fastify plugin registrations.modules
: Contains specific application features (like OTP handling). Each module typically has routes, services, schemas, etc.lib
: Holds shared utilities like the configured Prisma client.1.4 Setup Environment Variables:
Create
.env.example
and.env
files.Add
.env
anddist
to your.gitignore
:Obtaining Infobip Credentials:
xxxxx.api.infobip.com
) should be displayed.Application ID
.Your verification code is {{pin}}
), PIN type (Numeric), PIN length, etc. Save it. Copy the generatedMessage ID
.otp.service.ts
section, but for initial setup, using the portal is easier.1.5 Install Database (Prisma) & HTTP Client (Axios):
1.6 Initialize Prisma:
This creates the
prisma/schema.prisma
file and updates.env
with a placeholderDATABASE_URL
. Ensure your actualDATABASE_URL
in.env
points to your running PostgreSQL instance.1.7 Define Basic Prisma Schema:
Modify
prisma/schema.prisma
:Explanation:
OtpAttempt
model to store thepinId
returned by Infobip when an OTP is sent. This is crucial for the verification step. We link it to the phone number and add an expiry time.User
model is commented out – uncomment and adapt if you are building full user registration. Note the comment onphone @unique
: consider carefully if a phone number must be unique across all users in your application, as this might prevent legitimate scenarios like shared family numbers or number reuse over time.OtpAttempt.phone
to speed up lookups during verification.1.8 Create Initial Database Migration:
Ensure your database server is running and accessible using the
DATABASE_URL
specified in.env
.This command:
prisma/migrations
.OtpAttempt
table (andUser
table if uncommented).1.9 Setup Basic Fastify Server:
Create
src/server.ts
:1.10 Setup Environment Variable Loading:
First, install TypeBox dependencies:
Now, create
src/plugins/env.ts
:1.11 Setup Prisma Client Instance:
Create
src/lib/prisma.ts
:Project Setup Complete:
At this point, you have a basic Fastify application structure with TypeScript, environment variable loading, Prisma configured, and the initial database migration applied. You can run
npm run dev
to start the development server. It won't do much yet beyond a health check, but it confirms the basic setup is working.2. Implementing Core Functionality (OTP Service)
Now, let's implement the service that interacts with the Infobip 2FA API.
Create
src/modules/otp/otp.service.ts
: