Frequently Asked Questions
Implement 2FA in RedwoodJS by integrating the Plivo Messaging API, configuring Redis for OTP storage, and creating necessary frontend components. This involves setting up environment variables, modifying your Prisma schema, implementing API logic for sending and verifying OTPs, and building the necessary web components.
Plivo is a cloud communications platform that provides the SMS API capabilities for sending the One-Time Passwords (OTPs) to the user's mobile device during the Two-Factor Authentication process.
Redis, an in-memory data store, is used for temporary storage of OTPs due to its speed and automatic expiry feature. This enhances security by ensuring OTPs have a limited lifespan and are not persistently stored in a database.
2FA should be enabled after the user successfully verifies their phone number with an OTP. This ensures they control the provided number and are ready to use 2FA.
Set up Plivo by creating an account, purchasing an SMS-enabled phone number, obtaining API credentials (Auth ID and Auth Token), and storing these, along with your Plivo phone number, as environment variables in your RedwoodJS project's .env file.
Prisma, RedwoodJS's database toolkit, is used to modify the database schema. You need to add `phoneNumber` and `isTwoFactorEnabled` fields to your User model to support 2FA functionality.
For local development, use `redis://localhost:6379`. In production, replace this with the connection string provided by your Redis hosting service. Make sure this URL is stored securely in your environment variables.
Prerequisites include Node.js v18+, Yarn, RedwoodJS CLI, a Plivo account with an SMS-enabled number and API credentials, and a running Redis instance (local or cloud-hosted).
The system sends an OTP via Plivo to the user's phone number after initial login. The user then enters this OTP on the website, which is verified against the value stored in Redis. Upon successful verification, the user is granted full access.
Configure Plivo environment variables by adding `PLIVO_AUTH_ID`, `PLIVO_AUTH_TOKEN`, and `PLIVO_PHONE_NUMBER` (in E.164 format) to your `.env` file in the project root. Make sure to add `.env` to your `.gitignore` file.
Handle errors using try-catch blocks in your API service and onError callbacks in your web-side useMutation hooks. Log detailed errors server-side but provide generic, user-friendly error messages to the client for security.
Rate limiting on the requestOtp mutation is crucial to prevent SMS pumping attacks. This limits how frequently a user can request new OTPs, mitigating abuse.
Protect against brute-force attacks by tracking failed OTP verification attempts in Redis. After a set number of failed attempts, invalidate the OTP and potentially lock the user's account temporarily.
Yes, you can test both the API and web sides of your 2FA implementation. Use Jest to create unit and integration tests, mocking external services like Plivo and Redis for isolated testing.
This guide provides a complete walkthrough for integrating SMS-based Two-Factor Authentication (2FA) into your RedwoodJS application using the Plivo Messaging API. Adding 2FA enhances security by requiring users to provide a One-Time Password (OTP) sent to their mobile device, significantly reducing the risk of unauthorized account access even if passwords are compromised.
We will build a system where, after initial login (e.g., username/password - implementation not covered here), users are prompted to enter an OTP sent via SMS. We'll cover setting up RedwoodJS, configuring Plivo, implementing API logic for sending and verifying OTPs using Redis for temporary storage, and building the necessary front-end components.
Technologies Used:
System Architecture:
Prerequisites:
npm install -g redwoodjs@latest
redis://localhost:6379
).Final Outcome:
By the end of this guide, your RedwoodJS application will have a functional SMS 2FA flow. Users associated with a phone number will be required to verify an OTP sent via Plivo SMS before gaining full access after their initial login step.
1. Setting up the RedwoodJS Project
First, create a new RedwoodJS application if you don't have one already.
This command scaffolds a new RedwoodJS project with the necessary structure, including
api
andweb
sides.2. Plivo and Redis Configuration
We need to install the necessary libraries and configure environment variables to securely store credentials.
1. Install Dependencies:
Install the Plivo Node helper library and the Redis client library on the API side.
2. Configure Environment Variables:
RedwoodJS uses a
.env
file for environment variables. Create one in the project root if it doesn't exist. Add your Plivo credentials, Plivo phone number, and Redis connection URL.PLIVO_AUTH_ID
/PLIVO_AUTH_TOKEN
: Found on your Plivo Console dashboard. Used to authenticate API requests.PLIVO_PHONE_NUMBER
: The SMS-enabled number you purchased in Plivo, in E.164 format (e.g.,+14155551212
). This will be the sender ID for OTP messages. Replace the example value.REDIS_URL
: The connection string for your Redis instance. The exampleredis://localhost:6379
is suitable for local development but must be replaced with your production Redis URL when deploying.Important: Add
.env
to your.gitignore
file to prevent accidentally committing sensitive credentials. RedwoodJS automatically loads these variables.3. Database Schema Modifications
We'll assume you have a
User
model. We need to add fields to store the user's phone number (required for sending OTPs) and track their 2FA status.1. Edit Schema:
Modify your
api/db/schema.prisma
file:phoneNumber
: Stores the user's verified phone number in E.164 format (e.g.,+14155551212
). Make it unique if needed.isTwoFactorEnabled
: A flag indicating if the user has successfully set up and enabled 2FA.2. Apply Migrations:
Generate and apply the database migration:
This command updates your database schema to include the new fields.
4. API Side Implementation (Services & GraphQL)
Now, let's build the API logic for requesting and verifying OTPs.
1. Create Redis Client Utility:
Create a utility file to manage the Redis connection.
2. Create Plivo Client Utility:
Similarly, create a utility for the Plivo client.
3. Generate GraphQL Schema Definition (SDL) and Service:
Use the Redwood generator to scaffold the GraphQL types and mutations, along with the service file.
This command creates
api/src/graphql/twoFactorAuth.sdl.ts
andapi/src/services/twoFactorAuth/twoFactorAuth.ts
.4. Define GraphQL Mutations:
Modify the generated SDL file (
api/src/graphql/twoFactorAuth.sdl.ts
) to define the mutations for requesting and verifying OTPs.@requireAuth
: Ensures only authenticated users can call these mutations. Make sure yourapi/src/lib/auth.ts
is configured correctly, providingcurrentUser
in thecontext
.5. Implement Service Logic:
Now, implement the core logic in
api/src/services/twoFactorAuth/twoFactorAuth.ts
.Key points in the service:
requireAuth()
ensures only logged-in users access these functions.context.currentUser.id
retrieves the user ID (requires a functional Redwood Auth setup).redis.setex(key, expiry, otp)
stores the OTP with an expiration time (OTP_EXPIRY_SECONDS
).plivo.messages.create()
sends the SMS. Ensuresrc
(sender) anddst
(destination) numbers are in E.164 format.redis.get(key)
retrieves the stored OTP. It's compared against the user's input.redis.del(key)
) immediately after successful verification to prevent reuse.try...catch
blocks handle potential errors from Redis or Plivo, logging them and returning user-friendly errors viaRedwoodGraphQLError
.enableTwoFactorAuth
mutation updates the user's record in the database. It's recommended to add a check here to ensureverifyOtp
was successful recently (e.g., by checking a temporary flag set in Redis duringverifyOtp
).requestOtp
) or brute-force protection (onverifyOtp
), which are essential for production security.5. Web Side Implementation (Pages & Components)
Now, let's create the user interface for the 2FA challenge.
1. Generate Page and Component:
2. Define GraphQL Mutations for the Web:
Create a file to define the GraphQL mutations that the web side will use. Redwood's build process will use these definitions to generate types.
3. Implement the OTP Input Component (
OtpInputForm
):This component will contain the form elements.
4. Implement the Challenge Page (
TwoFactorChallengePage
):This page will display the
OtpInputForm
. You'll likely navigate to this page after a successful primary login if 2FA is enabled for the user.Integration Points:
isTwoFactorEnabled
andphoneNumber
.user.isTwoFactorEnabled
istrue
.requestOtp
mutation (API side).TwoFactorChallengePage
(/2fa-challenge
). You might pass thephoneNumberHint
as state during navigation if not easily accessible viauseAuth
on the challenge page.requestOtp
.OtpInputForm
).verifyOtp
.verifyOtp
succeeds: Call theenableTwoFactorAuth
mutation to setisTwoFactorEnabled = true
in the database. Confirm success to the user.verifyOtp
fails: Show an error and allow retries/resend.6. Security Considerations
requestOtp
mutation (API side) to prevent SMS Pumping fraud and abuse. Use Redis to track request timestamps per user ID or phone number and block excessive requests within a time window (e.g., max 1 request per 60 seconds, max 5 requests per hour). The provided code lacks this.setex
. Clearly communicate the expiry time to the user.verifyOtp
attempts for a given OTP/user. Track failed attempts in Redis (e.g., increment a counter with TTL). After N (e.g., 5) failures, invalidate the current OTP (redis.del(key)
) and force the user to request a new one. Consider temporary account lockouts after repeated failures across multiple OTPs. The provided code lacks this..env
) and ensure.env
is in.gitignore
. Use your deployment platform's secret management.+countrycodePhoneNumber
) for phone numbers when storing them and when interacting with the Plivo API.7. Error Handling and Logging
try...catch
blocks. Log detailed errors (including Plivo/Redis errors) using Redwood'slogger
. Return generic, user-friendly errors viaRedwoodGraphQLError
to avoid leaking sensitive details.onError
callbacks inuseMutation
hooks to display user-friendly messages usingtoast
or other UI elements. Log unexpected client-side errors.8. Testing
Unit Tests (API): Use Jest (built into Redwood) to test the service functions (
requestOtp
,verifyOtp
,enableTwoFactorAuth
). Mock the Plivo client (getPlivoClient
), Redis client (getRedisClient
), Prisma (db
), and authentication context (context.currentUser
,requireAuth
). Verify logic like OTP generation, Redis calls (setex
,get
,del
), Plivo calls (messages.create
), database updates, and return values under various success and failure conditions.Integration Tests: Test the flow end-to-end, from the web component triggering the mutation, through the API service, interacting with (mocked) Plivo/Redis, and updating the database. Redwood's testing utilities can help here.
Web Component Tests: Test the
OtpInputForm
andTwoFactorChallengePage
components using Jest and Redwood's testing setup (@redwoodjs/testing/web
). Verify rendering, form input handling, validation, state changes,useMutation
calls, and callbacks (onSuccess
). MockuseAuth
,navigate
,toast
, anduseMutation
.