Frequently Asked Questions
This guide details setting up 2FA using NestJS, Twilio, Prisma, and PostgreSQL. It covers user sign-up, login, phone verification, and 2FA-protected login. Key technologies include JWT for session management, bcrypt for password hashing, and Joi for validation.
NestJS is the core backend framework, providing structure, features, and TypeScript support for building the API. It's chosen for its modularity and efficiency in handling requests and coordinating with services like Twilio and Prisma.
Twilio is a reliable cloud communication platform. It's used to send OTPs via SMS for phone verification and two-factor authentication, adding an extra security layer to user logins.
Logging middleware is crucial for debugging and monitoring, providing context for each request. It's recommended to set this up early in the project for visibility into HTTP requests and responses, helping track issues in development and production.
Create a custom exception filter that implements NestJS's ExceptionFilter
interface. This allows you to catch and handle errors gracefully, providing consistent JSON error responses to clients, and logging errors with stack traces on the server-side, which is important for debugging and maintenance.
Install the Prisma CLI globally using npm install -D prisma
and the client library with npm install @prisma/client
. Initialize Prisma with npx prisma init --datasource-provider postgresql
. This sets up the necessary files for defining your data models.
Prisma is a modern database toolkit that simplifies database access with type safety and migrations in Node.js and TypeScript. It's used as the Object-Relational Mapper (ORM) to interact with the PostgreSQL database, managing connections and queries.
Generate a module and service using the NestJS CLI: nest generate module prisma
and nest generate service prisma
. In the service, extend the PrismaClient
and implement onModuleInit
to establish a database connection when the module initializes.
Creating a Prisma service follows SOLID principles and improves code organization and testability. It centralizes database logic, making it easier to manage database interactions within your NestJS application.
The project uses PostgreSQL, chosen for its reliability and robustness. The Prisma schema defines User and OTP models, which are migrated to the database.
Joi is a data validation library. You create a schema and use it with a custom pipe in your NestJS controller. This validates incoming requests, ensuring they adhere to the schema, and provides specific error messages for improved DX.
Hashing protects sensitive data even if the database is compromised. This guide uses bcrypt with a recommended salt round of 10. Never store passwords in plain text.
Generate an account module, controller, and service. Implement the signup logic in the service and define the POST route handler in the controller. Use a validation pipe (e.g., Joi) for data integrity.
Before creating a new user, query the database to check if a user with the given email or phone number already exists. If a duplicate is found, throw a ConflictException
to inform the client.
JWT (JSON Web Token) is used for stateless session management. After successful password verification, the server generates and returns a JWT, which the client uses for subsequent authenticated requests.
NestJS SMS 2FA with Twilio: Complete OTP Authentication Tutorial
Build secure SMS-based Two-Factor Authentication (2FA) in NestJS with Twilio. This comprehensive tutorial covers OTP phone verification, JWT authentication, Prisma ORM with PostgreSQL, bcrypt password hashing, and production-ready security practices including rate limiting and environment variable protection.
Last Updated: January 2025
What You'll Build
Build a secure NestJS backend application featuring:
Problem Solved: Add a critical security layer beyond passwords, mitigating risks from compromised credentials by requiring access to the user's registered phone.
Technologies Used:
System Architecture:
Your system comprises these interacting components:
Prerequisites:
Security Best Practices:
.env
to your.gitignore
file.Final Outcome: A functional NestJS API with secure user authentication, phone verification, and optional SMS-based 2FA.
How Do You Set Up the NestJS Project?
Scaffold your NestJS project and configure the basic utilities.
Security Note: Before you begin, create a
.gitignore
file in your project root to protect sensitive information:1.1 Install NestJS CLI:
Install the NestJS CLI globally if you haven't already.
1.2 Create New Project:
Generate a new NestJS project. Replace
nestjs-twilio-2fa
with your desired project name.This creates a standard NestJS project structure.
1.3 Initial Cleanup (Optional):
The default project includes sample controller and service files. Remove them for a clean start.
src/app.controller.ts
,src/app.service.ts
, andsrc/app.controller.spec.ts
.src/app.module.ts
and removeAppController
from thecontrollers
array andAppService
from theproviders
array.src/app.module.ts
(after cleanup):1.4 Set Global API Prefix:
Prefix your API routes (e.g.,
/api/v1
) for better organization.Edit
src/main.ts
:1.5 Configure Logging Middleware:
NestJS has built-in logging, but a custom middleware provides more request context.
Create
src/common/middleware/logger.middleware.ts
:Register the middleware globally in
src/app.module.ts
:1.6 Custom Exception Filter:
Handle errors gracefully and provide consistent error responses.
Create the custom filter (
src/common/filters/custom-exception.filter.ts
):Register the filter globally in
src/main.ts
:Why these choices?
How Do You Configure Prisma with PostgreSQL?
Set up PostgreSQL and use Prisma to interact with it.
2.1 Set up PostgreSQL Database:
Create your database using
psql
or a GUI tool.2.2 Install Prisma:
Install the Prisma CLI as a development dependency and the Prisma Client.
2.3 Initialize Prisma:
Create a
prisma
directory with aschema.prisma
file and a.env
file at the project root.2.4 Configure Database Connection:
Prisma automatically creates a
.env
file. Update theDATABASE_URL
with your PostgreSQL connection string..env
:YOUR_DB_USER
,YOUR_DB_PASSWORD
, etc.) with your actual database credentials.schema=public
is present if you're using the default schema.2.5 Define Data Models:
Open
prisma/schema.prisma
and define theUser
andOtp
models.prisma/schema.prisma
:Model Details:
twoFA
andisPhoneVerified
useCase
andexpiresAt
timestamp. Best Practice: OTPs should expire within 5-10 minutes to minimize security risks.expiresAt
for efficient cleanup of expired OTPs.2.6 Run Database Migration:
Apply the schema changes to your database. Prisma generates SQL migration files and runs them.
This command will:
prisma/migrations
folderUser
andOtp
tablesImportant: Whenever you change
prisma/schema.prisma
, runnpx prisma migrate dev --name <descriptive_migration_name>
to update your database andnpx prisma generate
to update the Prisma Client typings.2.7 Create Prisma Service:
Encapsulate Prisma Client logic within a dedicated NestJS service for better organization and testability.
Generate the module and service:
Implement the service (
src/prisma/prisma.service.ts
):Configure the module (
src/prisma/prisma.module.ts
):Import
PrismaModule
into the rootAppModule
(src/app.module.ts
):Enable shutdown hooks in
src/main.ts
:Why these choices?
PrismaService
easily injectable across the applicationHow Do You Implement User Registration?
Create the endpoints and logic for user registration.
3.1 Install Dependencies:
3.2 Create Account Module, Controller, Service:
3.3 Create Validation Pipe:
Create a pipe to validate incoming request bodies using Joi schemas.
Create
src/common/pipes/joi-validation.pipe.ts
:3.4 Create Sign-up DTO and Schema:
Define the expected request body structure.
Create
src/account/dto/create-user.dto.ts
:Create the Joi validation schema
src/account/validation/signup.schema.ts
:3.5 Create Password Hashing Utility:
Create
src/common/utils/password.util.ts
:3.6 Implement Sign-up Service Logic:
Inject
PrismaService
and implement the sign-up logic.Edit
src/account/account.service.ts
:3.7 Implement Sign-up Controller Endpoint:
Define the route handler in the controller.
Edit
src/account/account.controller.ts
:Ensure the
AccountService
andAccountController
are correctly listed insrc/account/account.module.ts
.src/account/account.module.ts
:Import
AccountModule
into the rootAppModule
(src/app.module.ts
):Why these choices?
How Do You Implement JWT Authentication?
Set up the initial login mechanism using email and password, returning a JWT if credentials are valid but before handling the 2FA OTP step.
4.1 Install JWT Dependency:
4.2 Configure JWT Module:
Define a JWT secret in your
.env
file. Use a strong, randomly generated secret in production..env
:Create a configuration file for easy access to constants (
src/common/config/config.ts
):Related Resources
NestJS Security:
Twilio Integration:
Database and ORM: