Frequently Asked Questions
Implement 2FA by integrating the Infobip 2FA API into your Node.js Express app. Create API endpoints to send OTPs via SMS to the user's phone number and then verify the code they enter. This enhances security by adding an extra layer of verification beyond passwords.
The Infobip 2FA API is the core service used for generating, sending, and verifying one-time passwords (OTPs) through various channels like SMS, voice calls, or email. This guide focuses specifically on using SMS for delivering OTPs.
Infobip simplifies OTP implementation by handling the complexities of generation, delivery, and verification. It allows developers to focus on their application logic rather than managing SMS gateways and other infrastructure.
To send an OTP, make a POST request to the /2fa/2/pin endpoint of the Infobip API. Provide the application ID, message ID, and the user's phone number in the request body.
The `pinId` should be removed from your data store immediately after successful verification. This prevents its reuse and enhances security. It's important to use a persistent data store like Redis or a database for managing `pinId`s in production.
No, using in-memory storage like the example's `activePinIds` is unsuitable for production. This is because data is lost on server restarts, and it doesn't work with multiple server instances. Use Redis or a database instead.
Verify the OTP by making a POST request to /2fa/2/pin/{pinId}/verify, providing the `pinId` (received from the send OTP request) and user-entered OTP. The response will indicate whether verification was successful.
The recommended format for phone numbers when using Infobip is E.164. This international standard format ensures consistent and reliable delivery. An example is +14155552671.
Improve error handling by using specific HTTP status codes (400 for bad input, 500 for server errors, 401 for invalid OTPs) and logging detailed errors server-side while providing generic messages to the client.
Use rate limiting to prevent brute-force attacks, validate inputs thoroughly, secure your API credentials, and ensure that the pinId is not exposed to the client in responses.
Returning the `pinId` to the client is a security risk. It's a server-side identifier. Exposing it unnecessarily could lead to potential misuse or information leakage if not carefully handled client-side.
Redis is recommended for storing `pinId`s due to its speed and built-in support for Time-To-Live (TTL), which automatically expires entries. A relational database can also be used but requires manual cleanup of expired entries.
Set up Infobip by creating a 2FA application and a message template in your Infobip account. You then store the application ID, message ID, API key, and base URL securely in your application's environment variables.
This guide utilizes Node.js with Express.js for the web server, the Infobip 2FA API for OTP services, Axios for HTTP requests, and dotenv for managing environment variables.
Two-factor authentication (2FA) adds a crucial layer of security to user accounts by requiring a second verification step beyond just a password. One common and user-friendly method for 2FA is using One-Time Passwords (OTP) sent via SMS.
This guide provides a complete walkthrough for implementing SMS-based OTP verification in a Node.js application using the Express framework and the Infobip 2FA API. We will build a simple API that can send an OTP to a user's phone number and then verify the code they enter.
Project Goals:
Technologies Used:
.env
file.System Architecture:
The basic flow involves our Node.js application interacting with the Infobip API. The Node.js app receives requests from the client, forwards OTP generation requests to Infobip, receives the
pinId
back, responds to the client that the OTP is sent, and later receives verification requests from the client to validate against Infobip. Infobip handles sending the SMS to the user after the Node.js app requests it.Prerequisites:
Final Outcome:
By the end of this guide, you will have a functional Node.js Express API with two endpoints:
/send-otp
and/verify-otp
. This API will leverage Infobip to handle the complexities of OTP generation, delivery via SMS, and verification, ready to be integrated into a larger application's authentication flow.1. Setting up the project
Let's start by creating our Node.js project and installing the necessary dependencies.
1. Create Project Directory: Open your terminal and create a new directory for the project.
2. Initialize Node.js Project: Initialize the project using npm (or yarn). This creates a
package.json
file.3. Install Dependencies: Install Express for the web server,
dotenv
for environment variable management, andaxios
for making HTTP requests to the Infobip API.4. Project Structure: Create the basic files and folders.
Your initial structure should look like this:
5. Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing sensitive information and dependencies.6. Basic Express Server Setup: Add the following initial code to
index.js
to set up a basic Express server.You can run this basic server to ensure setup is correct:
You should see
Server running on port 3000
in your console. You can stop the server withCtrl+C
.2. Infobip configuration
Before interacting with the Infobip API, we need to configure it within Infobip and store the necessary credentials securely in our application.
1. Obtain Infobip API Key and Base URL:
your-account.api.infobip.com
). The specific Base URL is usually displayed near your API keys or in the general API documentation entry point for your account.2. Store Credentials in
.env
: Open the.env
file and add your Infobip credentials.Replace
YOUR_COPIED_API_KEY
andYOUR_BASE_URL
with your actual values. Remember that the.env
file should not be committed to version control.3. Create Infobip 2FA Application: An Infobip
Application
defines the behavior and rules for your 2FA flow (like PIN attempts, validity time). You need to create one using the Infobip API.YOUR_BASE_URL
andYOUR_API_KEY
with your actual credentials in the command below.App
, followed by a space, then your API key:-H 'Authorization: App YOUR_API_KEY'
.curl
command:name
: A descriptive name for this 2FA configuration.pinAttempts
: Maximum number of verification attempts allowed for a single PIN.allowMultiplePinVerifications
: Whether the same PIN can be verified multiple times (useful for testing, potentially disable in production).pinTimeToLive
: How long the generated PIN is valid (e.g., ""10m"" for 10 minutes).verifyPinLimit
: Rate limit for verification attempts per PIN ID.sendPinPerApplicationLimit
: Rate limit for sending PINs across the entire application.sendPinPerPhoneNumberLimit
: Rate limit for sending PINs to a single phone number.enabled
: Whether this configuration is active.applicationId
: The API response will be a JSON object containing anapplicationId
. Copy this value..env
: Add the copiedapplicationId
to your.env
file:4. Create Infobip Message Template: This template defines the content of the SMS message sent to the user_ including the placeholder for the OTP code.
YOUR_BASE_URL
_YOUR_API_KEY
_ andYOUR_2FA_APP_ID
in the command below.senderId
is what appears as the sender on the user's phone. For trial accounts_ this might be restricted. For paid accounts_ you can often register a custom alphanumeric sender ID or use a purchased phone number. Check Infobip documentation for specifics in your region. Replace""InfoSMS""
with your desired/allowed sender ID.App YOUR_API_KEY
format.curl
command:messageText
: The SMS body.{{pin}}
is the mandatory placeholder for the OTP.pinLength
: The number of digits for the OTP (e.g._ 6).pinType
: The type of PIN (NUMERIC
_ALPHA
_HEX
_ALPHANUMERIC
).senderId
: The sender ID displayed to the user.language
: Helps with potential future localization or specific character encoding needs.messageId
: The API response will contain amessageId
. Copy this value..env
: Add the copiedmessageId
to your.env
file:Now your application has the necessary credentials and configuration IDs stored securely.
3. Implementing core functionality: Sending and Verifying OTPs
Let's write the functions that interact with the Infobip API. We'll create helper functions for sending and verifying OTPs.
1. Setup Axios Instance: It's good practice to create a pre-configured Axios instance for interacting with the Infobip API. Add this near the top of
index.js
:2. Send OTP Function: This function takes a phone number, calls the Infobip API to send the PIN, and stores the returned
pinId
associated with the phone number (temporarily in memory for this example).applicationId
andmessageId
.to
) as input. Ensure it's in the correct format (usually E.164, e.g.,+14155552671
or442071838750
). Infobip is generally flexible but E.164 is standard.POST
request to/2fa/2/pin
.pinId
from the successful response. ThispinId
is essential for the verification step.pinId
in our simpleactivePinIds
object, mapping the phone number to its latestpinId
. This is the part needing replacement for production.3. Verify OTP Function: This function takes the phone number, the user-submitted PIN, retrieves the corresponding
pinId
from the temporary store, and calls the Infobip API to verify the PIN.pinId
from ouractivePinIds
store using the phone number. Handles the case where nopinId
is found (e.g., expired or never sent).POST
request to/2fa/2/pin/{pinId}/verify
, including thepinId
in the URL and the user's submittedpin
in the body.verified
property in the response (true/false).pinId
from storage upon successful verification to prevent reuse. This relies on the temporary store.TOO_MANY_ATTEMPTS
or404 Not Found
(which usually indicates an expired/invalidpinId
).4. Building the API Layer
Now, let's expose the
sendOtp
andverifyOtp
functions through Express API endpoints.1. Define API Routes: Add the following route handlers in
index.js
before theapp.listen
call./send-otp
(POST): ExpectsphoneNumber
in the JSON body. CallssendOtp
. Returns a success message. Explicitly states the security reason for not returning thepinId
to the client. Basic phone number format validation added./verify-otp
(POST): ExpectsphoneNumber
and thepin
code in the JSON body. CallsverifyOtp
(which currently depends on the temporaryactivePinIds
store). Returns success (200) or failure (401) based on the result. Basic PIN and phone number validation added.2. Testing with
curl
or Postman:Start the server:
node index.js
Send OTP: (Replace
+1XXXXXXXXXX
with a valid phone number, preferably one associated with your Infobip trial account if applicable).Expected Response (200 OK):
You should receive an SMS on the target phone number.
Verify OTP: (Replace
+1XXXXXXXXXX
with the same phone number and123456
with the actual code you received).Expected Response (Success - 200 OK):
Expected Response (Incorrect PIN - 401 Unauthorized):
5. Error handling, logging, and retry mechanisms
We've added basic
try...catch
blocks and logging. Let's refine this.console.log
andconsole.error
. For production, use a dedicated logging library (like Winston or Pino) to:allowMultiplePinVerifications
is true, but it uses up an attempt)./send-otp
endpoint from the server-side automatically, as it could lead to multiple unwanted SMS messages and costs. If a send request fails, it's usually better to return an error to the client and let the user retry the action. For/verify-otp
, a limited retry (e.g., 1 retry on a 5xx error or timeout from Infobip) might be acceptable, but be cautious. Implement retries with exponential backoff (wait longer between each retry) using libraries likeaxios-retry
if deemed necessary.Example (Conceptual Logging Enhancement):
6. Database schema and data layer (The Production Necessity)
Our current implementation uses an in-memory object (
activePinIds
) to store the mapping between phone numbers and activepinId
s. This is explicitly unsuitable for production for the reasons highlighted in the warning block in Section 3 (data loss on restart, inability to scale).Production Approach: Replacing In-Memory Storage
You must replace the
activePinIds
object with a persistent and potentially shared data store before deploying. Common choices include:pinId
s because it has built-in support for Time-To-Live (TTL). You can set the TTL to match thepinTimeToLive
configured in Infobip (e.g., 10 minutes), and Redis will automatically expire the entry.otp:phoneNumber:+1XXXXXXXXXX
, Value:pinId:YYYYYYYYYY
, TTL: 600 seconds (for 10 min expiry).users
table or create a separateotp_attempts
table. This requires manual cleanup of expired entries (e.g., via a scheduled job).users
table enhancement):otp_pin_id
(VARCHAR, nullable): Stores the latest active pinId.otp_pin_expires_at
(TIMESTAMP, nullable): Stores when the pinId expires. (Requires logic to check expiry).otp_attempts
table):id
(PK)user_id
(FK to users, optional)phone_number
(VARCHAR, indexed)pin_id
(VARCHAR, unique)expires_at
(TIMESTAMP, indexed)verified_at
(TIMESTAMP, nullable)created_at
(TIMESTAMP) (Requires cleanup job).Implementation Sketch (using Redis with
ioredis
):Choose the approach that best fits your application's architecture and scale. Redis with TTL is generally the preferred method for this use case due to its performance and built-in expiration handling, simplifying the application logic.
7. Adding security features
Security is paramount for authentication flows.
verifyPinLimit
,sendPinPerPhoneNumberLimit
), but you should also implement limits in your API layer using middleware likeexpress-rate-limit
. Apply stricter limits to/verify-otp
than/send-otp
.express-validator
: For validating request bodies, params, and queries structure and types.libphonenumber-js
: For parsing, validating, and normalizing phone numbers thoroughly across different regions..env
is good for local development. In production, use secrets management systems provided by your cloud provider (AWS Secrets Manager, Google Secret Manager, Azure Key Vault) or tools like HashiCorp Vault. Never commit API keys or other secrets to Git.