Frequently Asked Questions
OTP-based 2FA is ideal when you need a strong, yet user-friendly second authentication factor. It's suitable for various applications, from securing online accounts to verifying financial transactions.
Yes, the example uses PostgreSQL with Sequelize, an ORM that supports other SQL databases as well. You can adapt the configuration to use MySQL, SQLite, MSSQL, and other compatible databases.
Implement OTP 2FA using Sinch's Verification API with Node.js and Express. This involves capturing user phone numbers during registration, sending OTPs via Sinch during login, and verifying the OTP before granting full access.
The Sinch Verification API is a service that simplifies the process of sending and verifying one-time passwords (OTPs). It supports delivery through both SMS and voice calls, managing international delivery and code generation complexities.
Two-factor authentication (2FA) improves login security by adding a 'possession factor' – something the user has (their phone). This makes it significantly harder for attackers to gain access even if they compromise a user's password.
Initialize a Node.js project, install required dependencies (Express, Sequelize, Sinch API client, bcrypt, etc.), create project directories (controllers, models, routes, services), and set environment variables.
bcrypt is used for securely hashing passwords before they are stored in the database. This protects sensitive user data even if the database is compromised.
Use the libphonenumber-js library to validate and format phone numbers during user registration. This ensures phone numbers are in a consistent, internationally recognized format.
The main problem solved is the vulnerability of relying solely on passwords, which can be easily guessed, phished, or leaked. 2FA adds a second layer of defense by requiring a code sent to the user's phone.
The project leverages Node.js, Express, Sinch Verification API, PostgreSQL, bcrypt, dotenv, express-validator, Helmet, express-rate-limit, axios, libphonenumber-js, and Pino.
Use express-rate-limit middleware to limit the number of login attempts from a single IP address within a time window. This helps prevent brute-force attacks.
The diagram visually represents the flow of requests and data during registration, login, and OTP verification. It shows how the user, the Node.js/Express backend, the database, and the Sinch API interact.
Tools like Postman or curl are recommended for sending test requests to the API endpoints. This lets you simulate registration, login, OTP requests, and verification scenarios.
You'll have a working backend API that registers users, securely logs them in with passwords and OTP verification via Sinch, and manages basic security measures.
How to Implement SMS Two-Factor Authentication with Sinch, Node.js & Express
Introduction
Implement SMS-based two-factor authentication (2FA) in your Node.js application using Sinch's Verification API and Express framework. Two-factor authentication adds a critical security layer to user logins by combining password credentials with phone verification, reducing unauthorized access risk by up to 99.9% according to Microsoft Security research.
SMS-based 2FA offers advantages over app-based authenticators: no additional app installation required, works on any mobile device, and provides immediate accessibility. While app-based TOTP (Time-based One-Time Password) offers stronger security against SIM-swapping attacks, SMS 2FA strikes the optimal balance between security and user convenience for most applications.
One-time passwords (OTP) delivered via SMS or voice call provide user-friendly 2FA that balances security and convenience. This comprehensive guide demonstrates step-by-step integration of Sinch's Verification API into a Node.js and Express backend for robust OTP-based authentication. You'll build a complete system where users register with phone numbers, log in with credentials, and verify their identity using Sinch-delivered OTP before gaining full access.
What You'll Build
Create a production-ready authentication system with these features:
Authentication Flow:
Security Problem Solved:
This implementation addresses password-only authentication vulnerabilities by adding a possession factor (the user's verified phone number) to the authentication process. Multi-factor authentication prevents common attack vectors including credential stuffing (using leaked passwords), phishing attacks (stolen passwords alone cannot grant access), and brute-force attacks (rate limiting combined with OTP requirement).
Required Technologies
Core Technologies (Verified January 2025):
Supporting Libraries:
dotenv.envfileexpress-validatorhelmetexpress-rate-limitaxioslibphonenumber-jspinoSystem Architecture:
Prerequisites
Before starting, ensure you have:
node --versionandnpm --version..envfile before running migrations.Install PostgreSQL (if needed):
brew install postgresql@14 && brew services start postgresql@14sudo apt update && sudo apt install postgresql-14Create PostgreSQL Database:
What You'll Learn:
By completing this guide, you will have a functional backend API capable of:
Technology Verification Date: All technology versions and requirements verified as of January 2025. Check official documentation for latest releases.
Set Up Your Node.js Project
Initialize your Node.js project and install all required dependencies for SMS-based two-factor authentication.
Step 1: Create Project Directory and Initialize
Open your terminal and run:
This creates a new directory, navigates into it, and initializes a
package.jsonfile with default settings.Step 2: Install Core Dependencies
Install Express, database interaction, security, environment variables, HTTP requests, logging, and phone number validation:
Package Purposes:
express: Web frameworkpg: PostgreSQL client for Node.js (used by Sequelize)sequelize: ORM for Node.jsbcrypt: Password hashing library (uses CPU-intensive key derivation to resist brute-force attacks)dotenv: Loads environment variables from.envexpress-validator: Input validation middleware (prevents SQL injection and XSS attacks)helmet: Security header middleware (sets CSP, HSTS, X-Frame-Options, etc.)express-rate-limit: Request rate limiting middlewareaxios: HTTP client for Sinch API callspino: JSON loggerlibphonenumber-js: Phone number validation/formattingStep 3: Install Development Dependencies
Install
nodemonfor automatic server restarts,sequelize-clifor migrations, andpino-prettyfor human-readable logs during development:Step 4: Configure
package.jsonScriptsAdd scripts to your
package.json:Note: npm automatically installs the latest compatible versions matching these semver ranges.
Step 5: Create Project Structure
Organize your project with this folder structure:
Create these directories:
Step 6: Configure
.gitignoreAdd
node_modules,.env, log files, build artifacts, and IDE configurations to.gitignore:Step 7: Set Up Environment Variables (
.env)Create the
.envfile in the project root with initial configuration:Important: Replace
your_postgres_passwordwith your actual PostgreSQL password. You must manually create the databasesinch_otp_dbbefore running migrations (see Prerequisites section).bcrypt Salt Rounds Explained: The
PASSWORD_SALT_ROUNDSvalue determines computational cost. Each increment doubles the time:Balance security with performance based on your server capacity.
Step 8: Configure Logger (
src/utils/logger.js)Set up Pino logger:
Step 9: Basic Server Setup (
src/index.js)Create a minimal Express server with security middleware:
CORS Configuration (if needed for frontend):
If you're building a separate frontend application, add CORS middleware:
Then add to
src/index.jsafter line 16:Step 10: Set Up Main Router (
src/routes/index.js)Create the main router:
Step 11: Set Up Placeholder Auth Routes (
src/routes/auth.routes.js)Step 12: Basic Error Handler (
src/middleware/errorHandler.js)Create a centralized error handler:
Step 13: Run the Server
Start the development server:
You should see log output:
Server running on http://localhost:3000. Test the health check endpoint:http://localhost:3000/health.Troubleshooting Common Startup Errors:
Cannot find module './routes'src/routes/index.js(Step 10)connect ECONNREFUSEDbrew services start postgresql@14(macOS) orsudo service postgresql start(Linux)password authentication failed.envDB_USERandDB_PASSWORDin.envEADDRINUSEPORTin.envor stop conflicting processImplement Database Models
Set up Sequelize ORM with PostgreSQL and create the User model for storing authentication data.
Step 1: Initialize Sequelize
Configure Sequelize to use custom directory structure:
Initialize Sequelize:
This creates the directory structure defined in
.sequelizerc.Step 2: Configure Database Connection
Sequelize CLI requires a JSON config file but cannot execute JavaScript. Create a JavaScript config module:
Update
.sequelizercto reference the JS file:Step 3: Create User Model and Migration
Generate the User model and migration:
Step 4: Configure Migration
Edit the generated migration file in
src/database/migrations/:Field Explanations:
Step 5: Configure User Model
Edit
src/database/models/user.jsto add password comparison and hashing hooks:Step 6: Run Migration
Apply the migration to create the
Userstable:Verify migration success:
You should see the
Userstable with all columns defined in the migration.Implement Sinch Verification Service
Create the service layer that communicates with Sinch's Verification API to send and verify OTP codes.
Step 1: Create Sinch Service (
src/services/sinch.service.js)Sinch API Response Formats:
Start Verification Response:
Verification Report Response (Success):
Verification Report Response (Failure):
Step 2: Update
.envwith Sinch CredentialsAdd your Sinch API credentials to
.env:Replace
your_actual_sinch_application_keyandyour_actual_sinch_application_secretwith credentials from your Sinch dashboard.Implement Authentication Service
Create the authentication service layer that handles user registration, login, OTP request, and OTP verification.
Step 1: Create Auth Service (
src/services/auth.service.js)Rate Limiting Explanation: The 2-minute delay between OTP requests prevents abuse while maintaining usability. Users who don't receive the first OTP can retry after 2 minutes. This protects against:
For high-security applications, consider increasing to 3–5 minutes.
Implement Controllers and Routes
Connect the authentication service to HTTP endpoints with input validation.
Step 1: Create Input Validation Middleware (
src/middleware/validation.js)Step 2: Implement Auth Controller (
src/controllers/auth.controller.js)Step 3: Update Auth Routes (
src/routes/auth.routes.js)Test Your Implementation
Test the complete authentication flow using Postman, Insomnia, or curl.
Base URL:
http://localhost:3000/api/v1/authStep 1: Register a New User
Expected Response (201 Created):
Step 2: Login with Credentials
Expected Response (200 OK):
The system automatically sends an OTP to your phone number.
Step 3: Verify OTP
Check your phone for the OTP code, then submit:
Expected Response (200 OK):
Step 4: Request OTP Manually (Optional)
If OTP doesn't arrive or expires:
Expected Response (200 OK):
Common Test Scenarios:
Implement JWT Authentication (Optional)
Add stateless session management using JSON Web Tokens.
Step 1: Install JWT Library
Step 2: Add JWT Secret to
.envGenerate secure JWT secret:
Step 3: Create JWT Utility (
src/utils/jwt.js)Step 4: Create Authentication Middleware (
src/middleware/auth.js)Step 5: Update Auth Controller to Issue Tokens
Modify
src/controllers/auth.controller.js:Step 6: Create Protected Route Example
Add protected endpoint to test authentication:
Test JWT Authentication:
Production Deployment Checklist
Prepare your application for production deployment.
Security Hardening:
JWT_SECRET(32+ characters, randomly generated)PASSWORD_SALT_ROUNDSto 12 or higher.envfiles (never commit to Git)*wildcard)Environment Configuration:
Deployment Steps:
Run Database Migrations:
Start Production Server:
Use Process Manager (PM2):
Monitoring and Logging:
Scaling Considerations:
Troubleshooting Common Issues
Issue: "Unable to connect to the database"
Causes:
.envSolutions:
Issue: "Sinch verification failed" or "401 Unauthorized"
Causes:
Solutions:
SINCH_APP_KEYandSINCH_APP_SECRETin.envIssue: "Password hashing too slow"
Causes:
PASSWORD_SALT_ROUNDSset too highSolutions:
Issue: "OTP not received"
Causes:
Solutions:
Issue: "Too many requests" error
Causes:
Solutions:
RATE_LIMIT_WINDOW_MSandRATE_LIMIT_MAX_REQUESTSin.envNext Steps and Enhancements
Immediate Improvements:
Advanced Features:
Recommended Libraries:
Security Best Practices:
npm auditConclusion
You've built a production-ready SMS-based two-factor authentication system using Sinch, Node.js, Express, and PostgreSQL. This implementation provides:
✅ User registration with email, password, and phone number ✅ Secure password hashing with bcrypt ✅ SMS OTP delivery via Sinch Verification API ✅ Complete authentication flow with verification ✅ Rate limiting and security middleware ✅ Structured logging for production monitoring ✅ Database persistence with Sequelize ORM ✅ Optional JWT token-based sessions
Key Takeaways:
Additional Resources:
For questions or issues, refer to the troubleshooting section or consult the official documentation for each technology.