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.
Secure your RedwoodJS application with SMS-based two-factor authentication (2FA) using the Plivo API. This comprehensive tutorial shows you how to implement RedwoodJS SMS 2FA with one-time password (OTP) verification, protecting user accounts even when passwords are compromised. Learn how to add an extra layer of security that reduces unauthorized access by up to 99.9% according to industry security reports.
You'll integrate Plivo's messaging API with RedwoodJS, configure Redis for temporary OTP storage, implement GraphQL mutations for sending and verifying codes, and build React components for the user interface. By the end, you'll have a production-ready 2FA system with proper security measures including rate limiting, brute-force protection, and secure credential management.
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@latestredis://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.
How Do You Set Up a RedwoodJS Project for SMS 2FA?
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
apiandwebsides.How Do You Configure Plivo and Redis for OTP Delivery?
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
.envfile 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:6379is suitable for local development but must be replaced with your production Redis URL when deploying.Important: Add
.envto your.gitignorefile to prevent accidentally committing sensitive credentials. RedwoodJS automatically loads these variables.How Do You Modify the Database Schema for 2FA?
We'll assume you have a
Usermodel. 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.prismafile: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.
How Do You Implement the API Logic for OTP Verification?
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.tsandapi/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.tsis configured correctly, providingcurrentUserin 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.idretrieves 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...catchblocks handle potential errors from Redis or Plivo, logging them and returning user-friendly errors viaRedwoodGraphQLError.enableTwoFactorAuthmutation updates the user's record in the database. It's recommended to add a check here to ensureverifyOtpwas 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.How Do You Build the Web Interface for OTP Challenge?
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:
isTwoFactorEnabledandphoneNumber.user.isTwoFactorEnabledistrue.requestOtpmutation (API side).TwoFactorChallengePage(/2fa-challenge). You might pass thephoneNumberHintas state during navigation if not easily accessible viauseAuthon the challenge page.requestOtp.OtpInputForm).verifyOtp.verifyOtpsucceeds: Call theenableTwoFactorAuthmutation to setisTwoFactorEnabled = truein the database. Confirm success to the user.verifyOtpfails: Show an error and allow retries/resend.What Security Measures Should You Implement?
requestOtpmutation (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.verifyOtpattempts 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.envis in.gitignore. Use your deployment platform's secret management.+countrycodePhoneNumber) for phone numbers when storing them and when interacting with the Plivo API.How Do You Handle Errors and Logging?
try...catchblocks. Log detailed errors (including Plivo/Redis errors) using Redwood'slogger. Return generic, user-friendly errors viaRedwoodGraphQLErrorto avoid leaking sensitive details.onErrorcallbacks inuseMutationhooks to display user-friendly messages usingtoastor other UI elements. Log unexpected client-side errors.How Do You Test Your 2FA Implementation?
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
OtpInputFormandTwoFactorChallengePagecomponents using Jest and Redwood's testing setup (@redwoodjs/testing/web). Verify rendering, form input handling, validation, state changes,useMutationcalls, and callbacks (onSuccess). MockuseAuth,navigate,toast, anduseMutation.Related Guides
To deepen your understanding of SMS authentication and RedwoodJS development, explore these related resources:
Frequently Asked Questions
How secure is SMS-based two-factor authentication?
SMS 2FA significantly improves security over passwords alone, reducing unauthorized access by up to 99.9%. However, it's vulnerable to SIM swapping and SMS interception. For maximum security, consider implementing app-based authenticators (TOTP) or hardware keys as alternatives or additional options alongside SMS.
What is the cost of implementing Plivo SMS 2FA?
Plivo charges per SMS sent, with pricing varying by destination country. Most regions cost between $0.0065-$0.02 per message. With proper rate limiting (preventing abuse) and assuming 2-3 OTP requests per successful login, costs typically range from $0.01-$0.06 per user login with 2FA enabled.
How do I handle users in countries where Plivo SMS doesn't work?
Implement fallback authentication methods like email-based OTP, authenticator apps (TOTP), or backup codes. Check Plivo's coverage before enabling 2FA for international users, and provide clear guidance on alternative verification methods in your UI.
Can I use this implementation with RedwoodJS dbAuth?
Yes, this tutorial is designed to integrate with RedwoodJS dbAuth. The
requireAuth()directive andcontext.currentUserwork seamlessly with dbAuth. You'll need to modify your login flow in the authentication service to trigger the OTP challenge after successful password verification.How do I prevent SMS pumping fraud with Plivo?
Implement aggressive rate limiting (max 3 OTP requests per hour per user), monitor unusual patterns (same IP requesting OTPs for multiple numbers), use CAPTCHA before OTP requests, validate phone numbers against known patterns, and set up Plivo spending alerts. Track costs in your monitoring dashboard.
What's the best OTP expiry time for security and user experience?
5 minutes (300 seconds) balances security and usability well. Shorter times (2-3 minutes) improve security but may frustrate users with delayed SMS delivery. Longer times (10+ minutes) reduce security. Always allow users to request a new code if theirs expires.
How do I implement rate limiting for OTP requests in Redis?
Store a counter in Redis with a key like
otp_requests:${userId}:${timeWindow}usingINCRand set expiry withEXPIRE. Check the counter before sending OTP - if it exceeds your threshold (e.g., 5 per hour), reject the request. This prevents abuse and SMS pumping fraud.Should I store phone numbers in E.164 format in the database?
Yes, always store phone numbers in E.164 format (
+14155551212). This ensures compatibility with Plivo API, enables accurate validation, prevents formatting issues across regions, and simplifies internationalization. Validate and normalize numbers to E.164 before storing.