Frequently Asked Questions
Set up 2FA in your RedwoodJS app by first creating a new RedwoodJS project using Yarn, configuring your database in schema.prisma, and then setting up dbAuth using Redwood's generator. This lays the foundation for integrating Twilio Verify for SMS-based OTPs.
Twilio Verify is a service that simplifies the process of sending and verifying one-time passwords (OTPs) for two-factor authentication (2FA). In this setup, it's used to send OTPs via SMS to enhance login security.
Twilio Verify is a managed service, providing reliability and scalability for sending and verifying OTPs. It's chosen for its ease of integration with various platforms, including RedwoodJS, and support for multiple channels like SMS.
Implement 2FA whenever enhanced security is a priority. It mitigates risks like phishing, brute-force attacks, and credential stuffing, which are common vulnerabilities of standard password authentication.
You can install the Twilio Node.js helper library using Yarn within the API workspace of your RedwoodJS project. This allows your backend to interact directly with the Twilio API for sending and verifying OTPs.
You need three environment variables from your Twilio account: TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_VERIFY_SERVICE_SID. These credentials are essential for authenticating and using the Twilio Verify service.
Create a Twilio Verify Service through your Twilio console. Enable SMS as the verification channel, set your desired code length (6 digits is recommended), and configure other settings like the Default Sender ID.
Your Twilio Account SID and Auth Token can be found on your main Twilio Console Dashboard under "Account Info." You may need to click "Show" or re-authenticate to reveal your Auth Token.
The Verify Service SID (starting with 'VA...') is a unique identifier for your Twilio Verify service configuration. It can be found on the settings page of the Verify service you created in the Twilio console.
Add twoFactorEnabled (Boolean) and phoneNumber (String, unique, nullable) fields to your User model in your schema.prisma file. These fields store the user's 2FA status and phone number.
Logging is crucial for debugging, monitoring, and security auditing. Use Redwood's built-in logger to track key events like successful verification, failed attempts, errors, and configuration issues for better visibility into the 2FA process.
RedwoodJS Services encapsulate backend logic, promoting reusability and testability. Using a service for Twilio interactions keeps 2FA logic organized and separate from other parts of your application.
Input validation prevents vulnerabilities. Use Redwood's validate and consider a library like libphonenumber-js to ensure phone numbers and OTP codes are in the correct format, preventing issues and potential exploits.
Implement try...catch blocks in your service file to handle Twilio API errors. Use Redwood's AuthenticationError and UserInputError to provide specific error feedback to the frontend without revealing sensitive details.
Implementing Two-Factor Authentication (2FA) with Twilio Verify in RedwoodJS
Time to complete: 2–3 hours | Skill level: Intermediate
This guide shows you how to add SMS-based One-Time Password (OTP) two-factor authentication (2FA) to your RedwoodJS application using Twilio Verify. You'll augment Redwood's built-in
dbAuthto require an OTP code after successful password validation for users who enable 2FA.Two-factor authentication blocks 99.9% of automated attacks by requiring both a password (something you know) and access to a registered phone (something you have).
Project Overview and Goals
What You'll Build:
dbAuthfor standard email/password authenticationProblem Solved: Standard password authentication exposes users to phishing, brute-force attacks, and credential stuffing. 2FA blocks these attacks by adding a second verification layer that attackers cannot bypass without physical access to the user's phone.
Technologies Used:
Technology Versions:
Final Outcome & Prerequisites:
1. Setting up the Project
Start with a fresh RedwoodJS project and configure
dbAuth.1.1 Create RedwoodJS App:
Open your terminal and run:
This command scaffolds a new RedwoodJS application with the complete folder structure, dependencies, and configuration files. Installation takes 2–5 minutes.
Troubleshooting: If yarn fails, ensure you have Node.js 18.x or later installed (
node --version). Clear the yarn cache withyarn cache cleanand retry.1.2 Configure Database:
Ensure your PostgreSQL database is running. Update the
providerandurlinapi/db/schema.prismaif you use a different database.Don't have PostgreSQL? Install it via Homebrew (
brew install postgresql), Docker (docker run -e POSTGRES_PASSWORD=password -p 5432:5432 postgres), or download from postgresql.org. For local testing only, switch to SQLite by changingproviderto"sqlite"andurlto"file:./dev.db".Update your
.envfile with your database connection string:Connection string format:
postgresql://[username]:[password]@[host]:[port]/[database_name]username: Your PostgreSQL username (default:postgres)password: Your PostgreSQL passwordhost: Database server address (uselocalhostfor local development)port: PostgreSQL port (default:5432)database_name: Name of your database1.3 Setup dbAuth:
Run the RedwoodJS
dbAuthsetup generator to scaffold login/signup pages and API functions:Files created:
web/src/pages/LoginPage/LoginPage.tsx– Login formweb/src/pages/SignupPage/SignupPage.tsx– Signup formapi/src/functions/auth.ts– Authentication handlerapi/src/lib/auth.ts– Auth configuration and utilitiesapi/db/schema.prisma– AddsUsermodel with authentication fields1.4 Apply Initial Migration:
Create and apply the initial database migration:
Success output: You should see "Migration applied successfully" and a new migration file in
api/db/migrations/.Troubleshooting: If the migration fails, verify your
DATABASE_URLis correct and the database server is running. Check that no other application is using the database.1.5 Install Twilio SDK:
Install the Twilio Node.js library in the API workspace:
You install
twilioin theapiworkspace (not the root) because only the backend interacts with the Twilio API. Installing it workspace-specific keeps your dependencies organized and reduces your frontend bundle size.1.6 Configure Environment Variables for Twilio:
Add your Twilio credentials to
.env(you'll get these in Section 4):Generate a strong SESSION_SECRET: Run
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"in your terminal and paste the output.Security: Never commit
.envto version control. Verify.envis listed in your.gitignorefile (RedwoodJS adds this by default).Project Structure Explanation:
api/: Backend code (database schema, services, GraphQL definitions)web/: Frontend code (React components, pages, layouts)scripts/: Seeding or other helper scripts.env: Environment variables (never commit this file)redwood.toml: Project configuration2. Implementing Core Functionality (API Service)
Create a Redwood Service to encapsulate all Twilio Verify logic. Services in RedwoodJS separate business logic from GraphQL resolvers, making code more testable, reusable, and maintainable.
2.1 Create Twilio Verify Service:
Generate a service for Twilio operations:
Files created:
api/src/services/twilioVerify/twilioVerify.ts– Service logicapi/src/services/twilioVerify/twilioVerify.scenarios.ts– Test data fixturesapi/src/services/twilioVerify/twilioVerify.test.ts– Unit tests (write tests here to verify your Twilio integration without making actual API calls)2.2 Implement Service Logic:
Open
api/src/services/twilioVerify/twilioVerify.tsand add the following code:Why this approach?
validate3. Building the API Layer (GraphQL)
Expose the service functions via GraphQL mutations so the frontend can trigger them.
3.1 Define GraphQL Schema:
Create a new SDL file for your Twilio Verify operations:
api/src/graphql/twilioVerify.sdl.ts.Important: The
@requireAuth(roles: ["USER_PENDING_2FA"])and associated mutations (requestOtpVerification,verifyOtpAndLogIn) are conceptual placeholders. Implementing the 2FA-during-login flow requires significant modifications to Redwood'sdbAuthlogic inapi/src/lib/auth.ts, which is beyond this guide's scope. Focus on theenable/disable/verifyOtpForSetupmutations for managing 2FA settings.What's missing: Complete implementation requires modifying
dbAuthto create a "pending 2FA" state after successful password authentication, then completing the session only after OTP verification. This involves custom session management and state tracking.3.2 Implement Resolvers:
Add the corresponding resolver functions to the
twilioVerifyservice file (api/src/services/twilioVerify/twilioVerify.ts).Important Considerations:
@requireAuth: Ensures only logged-in users can callenable/disable/verifyOtpForSetuprequestOtpVerification,verifyOtpAndLogIn): These are marked conceptual because properly integrating them requires modifying Redwood's coredbAuthauthentication flow (api/src/lib/auth.ts). This involves:authenticatefunction inauth.tsuser.twoFactorEnabledis true, prevent the immediate return of the full user sessionUSER_PENDING_2FA)requestOtpVerification(which needs the special role/state)verifyOtpAndLogIn(also needs the special role/state)verifyOtpAndLogInmust then correctly finalize the session setup upon successful OTP verification, returning the structure expected bydbAuthapi/src/lib/auth.tsis complex and requires careful security considerations – beyond this guide's scope. This guide focuses on the mechanics of enabling/disabling 2FA and interacting with Twilio.AuthenticationErrorfor auth-related issues andUserInputErrorfor validation problems, providing clearer feedback to the frontend without leaking internal detailsvalidate. Consider more robust phone number validation (e.g.,libphonenumber-js) for production environments4. Integrating with Third-Party Services (Twilio)
Get the necessary credentials from Twilio.
4.1 Sign Up/Log In to Twilio:
Free trial limitations: Trial accounts include $15.50 USD in credit. You can send SMS to verified numbers only (numbers you register in your Twilio console). Production accounts can send to any number but incur per-message costs (approximately $0.0075 per SMS in the US).
4.2 Find Account SID and Auth Token:
Navigate to your main Twilio Console Dashboard (https://console.twilio.com/)
Your Account SID and Auth Token appear prominently on the dashboard under "Account Info"
Copy these values into your
.envfile forTWILIO_ACCOUNT_SIDandTWILIO_AUTH_TOKEN4.3 Create a Twilio Verify Service:
4.4 Find Verify Service SID:
After creating the service, you'll land on its settings page
The Service SID (starting with
VA...) appears at the topCopy this value into your
.envfile forTWILIO_VERIFY_SERVICE_SID4.5 Security: Handling API Keys:
.envfile to version control (Git). Ensure.envis listed in your.gitignorefile (Redwood adds this by default)Environment Variables Explained:
TWILIO_ACCOUNT_SID: Your main Twilio account identifierTWILIO_AUTH_TOKEN: Your secret key for authenticating API requests to Twilio – treat it like a passwordTWILIO_VERIFY_SERVICE_SID: Identifies the specific Verify configuration (code length, channels, etc.) you want to use within your Twilio account5. Error Handling, Logging, and Retry Mechanisms
Error Handling Strategy:
try...catchblocks intwilioVerify.ts). Handle specific errors like 404 on verification checks to return meaningful statuses ('incorrect')AuthenticationErrorfor auth/permission issues andUserInputErrorfor bad input (like invalid phone format or incorrect OTP), providing clear error messages to the frontendtwilio-nodelibrary throws errors with properties likestatus(HTTP status code) andcode(Twilio error code). Log these details for debugging (logger.error({ error }, 'message'))Common Twilio Error Codes:
Logging:
logger(import { logger } from 'src/lib/logger')info: Sending verification, checking verification, enabling/disabling 2FA success, successful login completion (within the modifiedauth.ts)warn: Failed verification checks (e.g., wrong code returned status'incorrect'), attempts to use 2FA when not configured, failed login attempts due to incorrect OTPerror: Twilio API errors (non-404s during checks), database errors, unexpected exceptions, configuration errors (missing env vars) – include the error object in the log contextapi/src/lib/logger.tsand forward logs to a dedicated logging service (Datadog, Logtail, Papertrail, etc.) for analysis and alertingRetry Mechanisms:
sendVerificationCodefails due to a transient network issue, a very limited server-side retry (1–2 attempts with exponential backoff) might be acceptable. However, it's simpler and safer to let the user trigger a "Resend Code" action on the frontend if the first attempt failscheckVerificationCode) automatically is strongly discouraged as it facilitates brute-force attacks. Rely on Twilio's built-in rate limiting for verification checks. Frontend should allow the user to re-enter the code, but the backend should not retry the check automatically on failureTwilio Rate Limits:
Example Testing Error Scenarios:
enableTwoFactorverifyOtpForSetup.envto test configuration error handling in the service6. Database Schema and Data Layer
Update the
Usermodel to store 2FA-related information.6.1 Update Prisma Schema:
Modify the
Usermodel inapi/db/schema.prisma:Explanation:
twoFactorEnabled: A boolean flag indicating if the user has successfully set up and enabled 2FA – defaults tofalsephoneNumber: Stores the user's verified phone number in E.164 format (e.g.,+15551234567). It's nullable because users without 2FA won't have one stored. Making it@uniqueprevents two users from registering the same phone number for 2FA (consider implications if shared numbers are a valid use case, such as family accounts)6.2 Create and Apply Migration:
Generate a new migration file reflecting these schema changes and apply it to your database:
Rollback instructions: If the migration fails or you need to revert, run
yarn rw prisma migrate resolve --rolled-back [migration_name]and manually restore your database from a backup.Data Access:
dbimported fromsrc/lib/db) is used in thetwilioVerifyservice resolvers to read and update thetwoFactorEnabledandphoneNumberfields during the enable/disable/verify flowsPerformance/Scale:
@uniqueconstraint onphoneNumberautomatically creates a database index, ensuring efficient lookups if neededHandling Existing Users: If you deploy this to a production database with existing users, all users will have
twoFactorEnabledset tofalseandphoneNumberset tonullby default. Users can opt-in to 2FA through their profile settings.7. Adding Security Features
Security is paramount for authentication flows.
7.1 Input Validation and Sanitization:
libphonenumber-jsrecommended) to ensure E.164 format before sending to Twilio or storing. Use Redwood'svalidatefor basic presence/format checks as a first line of defense in resolversInstall libphonenumber-js:
Enhanced phone validation:
validate7.2 Protection Against Common Vulnerabilities:
@redwoodjs/api-serverpackage. Ensure it remains enabled (this is the default)dangerouslySetInnerHTMLunless absolutely necessary and sanitize content with a library like DOMPurifySession Hijacking Prevention:
dbAuthdoes this by default)api/src/lib/auth.ts)Backup Codes/Recovery Mechanism: Implement backup codes users can generate and store securely. If they lose phone access, they can use a backup code to log in and reconfigure 2FA:
Generate backup codes on 2FA setup:
Security Testing Recommendations:
Secure,HttpOnly, andSameSiteattributesSummary
This guide provided a comprehensive walkthrough for implementing SMS-based 2FA with Twilio Verify in RedwoodJS. You learned how to:
dbAuthWhat's not covered (requires additional implementation):
api/src/lib/auth.ts)Next steps:
dbAuthto integrate 2FA into the login flowProduction checklist:
Secure,HttpOnly,SameSiteattributes