Frequently Asked Questions
Implement SMS OTP 2FA using Express.js, the Infobip 2FA API, and environment variables for API keys. This setup allows you to send and verify one-time passwords, enhancing security for actions like login or registration by requiring a second verification step beyond a password.
The Infobip 2FA API is used for sending and verifying one-time passwords (OTPs) via various channels like SMS, voice calls, or email. This guide focuses on SMS OTPs, adding an extra layer of security to user accounts.
The `express-rate-limit` middleware helps protect your Node.js application from abuse by limiting the number of OTP requests and verification attempts from a single IP address within a timeframe, enhancing security.
Always use environment variables for sensitive information like API keys. The dotenv module in Node.js helps manage environment variables, ensuring your credentials are not exposed in your source code.
Send a POST request to Infobip's /2fa/2/pin endpoint using a library like axios. Provide your application ID, message ID, and user's phone number. The response contains a pinId, which should be stored securely on the server.
Use the pinId received from the send OTP request, store it securely on the server, and the user-provided OTP to make a POST request to Infobip's /2fa/2/pin/{pinId}/verify endpoint. This process confirms if the OTP is valid.
The pinId is a unique identifier for each OTP sent by Infobip's 2FA API. It's crucial for server-side tracking and verifying the correct OTP against the user's attempt, maintaining security and preventing unauthorized access.
Rate limiting protects against brute-force attacks and prevents abuse. It limits how many OTP requests can be made within a certain timeframe, enhancing security.
Clean up the stored `pinId` and other OTP-related data immediately after successful verification or after a defined number of failed attempts, or upon expiry. This is important for security best practices.
No, storing pinId in an in-memory object like otpStore is NOT suitable for production. Use a database (like Redis, PostgreSQL, MongoDB) or proper session management (like express-session) with a persistent store instead.
Essential libraries include express for the web framework, dotenv for managing environment variables, axios for making API requests, and express-rate-limit for security. Optionally, a database connector or session management library is needed for production.
Handle errors by checking the error response from the Infobip API. For expected errors like WRONG_PIN, return false. For other errors, log details and return generic error messages to the client, protecting sensitive information.
The project includes server.js for the main application, infobipService.js for API interaction logic, .env for configuration, .gitignore for version control, node_modules, package.json, and package-lock.json. This structure organizes the core components of the application.
Two-Factor Authentication (2FA) adds a crucial layer of security to user accounts by requiring a second form of verification beyond just a password. One of the most common 2FA methods is One-Time Passwords (OTP) delivered 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 Express application with endpoints to request an OTP via SMS and verify the OTP entered by the user.
Project Goals:
Technologies Used:
.env
file.pinId
. We'll use a simple in-memory store for demonstration, but provide schema guidance for a real database.System Architecture:
Prerequisites:
curl
).1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
1.1 Create Project Directory
Open your terminal and create a new directory for the project:
1.2 Initialize npm
This creates a
package.json
file.1.3 Install Dependencies
express
: The web framework.dotenv
: To manage environment variables for API keys and configurations.axios
: To make HTTP requests to the Infobip API.express-rate-limit
: To prevent abuse of OTP endpoints.1.4 Project Structure
Create the following basic structure:
1.5 Configure
.gitignore
Create a
.gitignore
file in the root directory and addnode_modules
and.env
to prevent committing them to version control:1.6 Create
.env
FileCreate a
.env
file in the root directory. Populate this with your Infobip credentials and configuration obtained in Section 4.Purpose of Configuration: Using environment variables (
dotenv
) is crucial for security and flexibility. It keeps sensitive credentials like API keys out of your source code and allows different configurations for development, staging, and production environments.2. Implementing Core Functionality (Infobip Interaction)
We need functions to interact with the Infobip 2FA API: sending the OTP and verifying it. Let's create helper functions for these actions. It's good practice to encapsulate third-party API interactions.
Create a new file,
infobipService.js
:Explanation:
axios
and load necessary environment variables usingdotenv.config()
.axios
instance (infobipAxios
) is created with the base URL and default headers (Authorization, Content-Type, Accept) required by Infobip. The API key is included in theAuthorization
header.sendOtp
:phoneNumber
as input./2fa/2/pin
endpoint.applicationId
,messageId
, andto
(phone number).pinId
from the response. ThispinId
must be kept server-side and associated securely with the user's session or verification attempt.verifyOtp
:pinId
(retrieved from server-side storage) and theotpCode
entered by the user./2fa/2/pin/{pinId}/verify
endpoint.pin
(user's code) in the request body.true
ifresponse.data.verified
is true.WRONG_PIN
,PIN_EXPIRED
,TOO_MANY_ATTEMPTS
and returnsfalse
instead of throwing an error, as these are expected verification outcomes.3. Building the API Layer (Express Routes)
Now, let's set up the Express server and create the API endpoints.
Update
server.js
:Explanation:
helmet()
adds basic security headers.express.json()
parses JSON bodies.express-rate-limit
instances (otpSendLimiter
,otpVerifyLimiter
) are applied to the respective routes. This is crucial for security. Adjust limits as needed.otpStore
): An in-memory objectotpStore
is used for demonstration only. This is NOT production-ready. It linkspinId
tophoneNumber
.express-session
would be used in a real application. The key steps are:express-session
with a secret and persistent store.sendOtp
succeeds, storepinId
, associatedphoneNumber
, andtimestamp
inreq.session
./verify-otp
, retrieve these details fromreq.session
, perform necessary checks (e.g., doesreq.body.phoneNumber
matchreq.session.otpPhoneNumber
?), and then callverifyOtp
./api/send-otp
Route (POST):otpSendLimiter
.phoneNumber
(emphasizing need for better validation).infobipService.sendOtp
.pinId
server-side (usingotpStore
demo orreq.session
).pinId
./api/verify-otp
Route (POST):otpVerifyLimiter
.phoneNumber
andotpCode
.pinId
from server-side storage (otpStore
demo orreq.session
).infobipService.verifyOtp
with the retrievedpinId
and the user-providedotpCode
.isVerified
is true, cleans up storage and returns success. This is the point to grant access/complete the action.isVerified
is false (wrong code, expired on Infobip), returns a400
error..env
, starts the Express server, logs essential information, including a warning if Infobip variables aren't set.module.exports = app;
is added to allow importing theapp
instance for automated testing (see Section 13).4. Integrating with Infobip (Configuration Details)
This step involves getting your credentials from Infobip and setting up the necessary 2FA Application and Message Template.
4.1 Obtain Infobip API Key and Base URL
Log in to your Infobip Portal.
Your Base URL is usually displayed prominently on the homepage after login, often within an ""API Key Management"" or ""Developer Tools"" section, or mentioned in the API documentation landing page specific to your account. It will look something like
xxxxxx.api.infobip.com
. Find the correct URL for your account region.Navigate to the API Key management section (typically found under your account settings, developer tools, or a dedicated ""API"" menu item).
Create a new API key. Give it a descriptive name (e.g., ""Node OTP App Key"").
Securely copy the generated API Key immediately. You will not be able to view it again after closing the creation dialog. Store it safely.
Update your
.env
file (created in Section 1.6) with these values:4.2 Create Infobip 2FA Application and Message Template
You need an Application and a Message Template within Infobip to define the behavior (PIN length, expiry, attempts) and content of your OTP messages. You can usually do this via the Infobip API or potentially through their web portal (the availability and location of UI configuration for 2FA might change, check their documentation or portal interface). We outline the API approach here.
Using Infobip Portal (If Available): Explore the portal for sections like ""Apps"", ""Channels"", ""Verify"", or ""2FA"". Look for options to create a new ""Application"" (specifically for 2FA/Verify) and associated ""Message Templates"". Configure settings like PIN type, PIN length, validity time (
pinTimeToLive
), allowed attempts (pinAttempts
), and the message text (critically, include the{{pin}}
placeholder where the code should appear). If you create them via the UI, carefully note down the generated Application ID and Message ID.Using API (Recommended for Automation/Consistency): Use
curl
, Postman, or an HTTP client in your preferred language with the Base URL and API Key obtained in Section 4.1. Note: Always refer to the latest Infobip 2FA API Documentation for the most current endpoints and request/response structures.a) Create 2FA Application: Send a POST request to
https://<YOUR_BASE_URL>/2fa/2/applications
. Replace<YOUR_BASE_URL>
with the value you added to your.env
file.Request (
curl
example - replace placeholders!):Adjust configuration values (like
pinAttempts
,pinTimeToLive
, rate limits) based on your security requirements.Example Successful Response:
Copy the
applicationId
from the response.b) Create 2FA Message Template: Send a POST request to
https://<YOUR_BASE_URL>/2fa/2/applications/<YOUR_APPLICATION_ID>/messages
. Replace<YOUR_BASE_URL>
and<YOUR_APPLICATION_ID>
with your Base URL and the Application ID you just received.Request (
curl
example - replace placeholders!):Ensure
{{pin}}
is included inmessageText
. AdjustpinLength
,pinType
as needed.senderId
might need configuration/approval depending on the destination country.Example Successful Response:
Copy the
messageId
from the response.4.3 Update
.env
FileAdd the obtained
applicationId
andmessageId
to your.env
file:Explanation of Variables:
INFOBIP_BASE_URL
: The unique API endpoint URL provided by Infobip for your account.INFOBIP_API_KEY
: Your secret key for authenticating API requests. Treat this like a password.INFOBIP_2FA_APP_ID
: Identifies the specific 2FA application configuration (rate limits, attempts, expiry) to use.INFOBIP_2FA_MSG_ID
: Identifies the specific message template (text, PIN length, sender ID) to use when sending the OTP SMS.Now your application is configured to communicate with the correct Infobip resources using the settings you defined.
5. Implementing Error Handling, Logging, and Retries
Error Handling Strategy:
infobipService.js
functions now attempt to catch specific API errors from Infobip (inspectingerror.response.data
). Expected failures likeWRONG_PIN
orPIN_EXPIRED
during verification result inverifyOtp
returningfalse
, which the route handler translates into a user-friendly 400 response. Other API errors (config, auth, network) are caught and logged server-side, resulting in a 500 response with a generic message.try...catch
blocks are used in route handlers and service functions to catch unexpected JavaScript errors or issues within the application logic. A basic global error handler is added toserver.js
as a fallback.message
orerror
fields suitable for client-side display (e.g., ""Invalid or expired OTP code."", ""Too many requests...""). Sensitive internal details are not exposed to the client.(Code in
infobipService.js
andserver.js
includes improved error handling.)Logging:
Use a dedicated logging library in production (like
winston
orpino
) for structured, leveled logging (info, warn, error) and easy integration with log management systems. For this guide, we useconsole.log
,console.warn
, andconsole.error
.pinId
server-side only), verification attempt, verification result (success/failure).