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
This guide provides a step-by-step walkthrough for adding SMS-based One-Time Password (OTP) two-factor authentication (2FA) to your RedwoodJS application using Twilio Verify. We'll augment Redwood's built-in
dbAuth
to require an OTP code after successful password validation for users who have enabled 2FA.This enhances security by requiring users to possess both their password (something they know) and access to their registered phone (something they have).
Project Overview and Goals
What We'll Build:
dbAuth
for standard email/password authentication.Problem Solved: Standard password authentication is vulnerable to phishing, brute-force attacks, and credential stuffing. Adding 2FA significantly mitigates these risks by adding a second verification layer.
Technologies Used:
Final Outcome & Prerequisites:
1. Setting up the Project
We'll start with a fresh RedwoodJS project and configure
dbAuth
.1.1 Create RedwoodJS App:
Open your terminal and run:
1.2 Configure Database:
Ensure your PostgreSQL database is running. Update the
provider
andurl
inapi/db/schema.prisma
if necessary (default is PostgreSQL).Update your
.env
file with your actual database connection string:1.3 Setup dbAuth:
Run the RedwoodJS
dbAuth
setup generator. This scaffolds basic login/signup pages and API functions.This command modifies files on both the
web
andapi
sides and adds a basicUser
model to yourschema.prisma
.1.4 Apply Initial Migration:
Create and apply the initial database migration, which includes the
User
table generated bydbAuth
.1.5 Install Twilio SDK:
Install the necessary Twilio Node.js helper library in the API workspace. Redwood automatically handles loading variables from the
.env
file.1.6 Configure Environment Variables for Twilio:
You'll need three pieces of information from your Twilio account (covered in Section 4): Account SID, Auth Token, and Verify Service SID. Add these to your
.env
file.Project Structure Explanation:
api/
: Holds backend code (database schema, services, GraphQL definitions).web/
: Holds frontend code (React components, pages, layouts).scripts/
: For seeding or other helper scripts..env
: Stores environment variables securely (DO NOT commit this file).redwood.toml
: Project configuration.We install
twilio
in theapi
workspace because only the backend needs to interact directly with the Twilio API.2. Implementing Core Functionality (API Service)
We'll create a Redwood Service to encapsulate the logic for interacting with Twilio Verify.
2.1 Create Twilio Verify Service:
Use the Redwood generator to create a service for Twilio operations.
This creates
api/src/services/twilioVerify/twilioVerify.ts
and related test/scenario files.2.2 Implement Service Logic:
Open
api/src/services/twilioVerify/twilioVerify.ts
and add the following code:Why this approach?
validate
.3. Building the API Layer (GraphQL)
We expose the service functions via GraphQL mutations so the frontend can trigger them.
3.1 Define GraphQL Schema:
Create a new SDL file for our Twilio Verify operations:
api/src/graphql/twilioVerify.sdl.ts
.Note: The
@requireAuth(roles: ["USER_PENDING_2FA"])
and the associated mutations (requestOtpVerification
,verifyOtpAndLogIn
) are conceptual. Implementing the 2FA-during-login flow requires significant modifications to Redwood'sdbAuth
logic inapi/src/lib/auth.ts
, which is beyond the scope of this guide. For now, focus on theenable
/disable
/verifyOtpForSetup
mutations for managing 2FA settings.3.2 Implement Resolvers:
Add the corresponding resolver functions to the
twilioVerify
service file (api/src/services/twilioVerify/twilioVerify.ts
).Important Considerations:
@requireAuth
: Ensures only logged-in users can callenable/disable/verifyOtpForSetup
.requestOtpVerification
,verifyOtpAndLogIn
): These are marked conceptual because properly integrating them requires modifying Redwood's coredbAuth
authentication flow (api/src/lib/auth.ts
). This involves:authenticate
function inauth.ts
.user.twoFactorEnabled
is true, prevent the immediate return of the full user session.USER_PENDING_2FA
).requestOtpVerification
(which needs the special role/state).verifyOtpAndLogIn
(also needs the special role/state).verifyOtpAndLogIn
must then correctly finalize the session setup upon successful OTP verification, returning the structure expected bydbAuth
.api/src/lib/auth.ts
is a complex task requiring careful security considerations and is outside the scope of this guide. This guide focuses on the mechanics of enabling/disabling 2FA and interacting with Twilio.AuthenticationError
for auth-related issues andUserInputError
for validation problems, providing clearer feedback to the frontend without leaking internal details.validate
. Consider more robust phone number validation (e.g.,libphonenumber-js
) for production environments.4. Integrating with Third-Party Services (Twilio)
Let's get the necessary credentials from Twilio.
4.1 Sign Up/Log In to Twilio:
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 are displayed prominently on the dashboard under ""Account Info"".
Copy these values into your
.env
file forTWILIO_ACCOUNT_SID
andTWILIO_AUTH_TOKEN
.4.3 Create a Twilio Verify Service:
4.4 Find Verify Service SID:
After creating the service, you'll be taken to its settings page.
The Service SID (starting with
VA...
) is displayed at the top.Copy this value into your
.env
file forTWILIO_VERIFY_SERVICE_SID
.4.5 Security: Handling API Keys:
.env
file to version control (Git). Ensure.env
is listed in your.gitignore
file (Redwood adds this by default).Environment Variables Explained:
TWILIO_ACCOUNT_SID
: Your main Twilio account identifier.TWILIO_AUTH_TOKEN
: Your secret key for authenticating API requests to Twilio. Treat it like a password.TWILIO_VERIFY_SERVICE_SID
: Identifies the specific Verify configuration (code length, channels, etc.) you want to use within your Twilio account.5. Error Handling, Logging, and Retry Mechanisms
Error Handling Strategy:
try...catch
blocks intwilioVerify.ts
). Handle specific errors like 404 on verification checks to return meaningful statuses ('incorrect'
).AuthenticationError
for auth/permission issues andUserInputError
for bad input (like invalid phone format or incorrect OTP), providing clear error messages to the frontend.twilio-node
library throws errors with properties likestatus
(HTTP status code) andcode
(Twilio error code). Log these details for debugging (logger.error({ error }, 'message')
).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 OTP.error
: Twilio API errors (non-404s during checks), database errors, unexpected exceptions, configuration errors (missing env vars). Include the error object in the log context.Retry Mechanisms:
sendVerificationCode
fails due to a potential transient network issue between your server and Twilio, a very limited server-side retry (1-2 attempts with backoff) might be acceptable. However, it's often simpler and safer to let the user trigger a "Resend Code" action on the frontend if the first attempt fails visibly.checkVerificationCode
) automatically is strongly discouraged as it can facilitate 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 failure.Example Testing Error Scenarios:
enableTwoFactor
.verifyOtpForSetup
..env
to test configuration error handling in the service.6. Database Schema and Data Layer
We need to update the
User
model to store 2FA-related information.6.1 Update Prisma Schema:
Modify the
User
model inapi/db/schema.prisma
:Explanation:
twoFactorEnabled
: A boolean flag indicating if the user has successfully set up and enabled 2FA. Defaults tofalse
.phoneNumber
: 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@unique
prevents two users from registering the same phone number for 2FA (consider implications if shared numbers are a valid use case).6.2 Create and Apply Migration:
Generate a new migration file reflecting these schema changes and apply it to your database.
Data Access:
db
imported fromsrc/lib/db
) is used in thetwilioVerify
service resolvers to read and update thetwoFactorEnabled
andphoneNumber
fields during the enable/disable/verify flows.Performance/Scale:
@unique
constraint onphoneNumber
automatically creates a database index, ensuring efficient lookups if needed.7. Adding Security Features
Security is paramount for authentication flows.
7.1 Input Validation and Sanitization:
libphonenumber-js
recommended, see Section 8) to ensure E.164 format before sending to Twilio or storing. Use Redwood'svalidate
for basic presence/format checks as a first line of defense in resolvers.validate
.7.2 Protection Against Common Vulnerabilities:
dangerouslySetInnerHTML
.