Implementing SMS OTP Verification with Fastify and Infobip - code-examples -

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.