How to add MFA to your homegrown auth using WorkOS
Learn how to add Multi-Factor Authentication (MFA) to your homegrown authentication system using WorkOS, with detailed code examples for TOTP and SMS-based flows.
Multi-Factor Authentication (MFA) adds a vital security layer to your authentication system. In this guide, you'll learn how to integrate WorkOS’s MFA API into a homegrown authentication system using either TOTP (Time-based One-Time Password) or SMS-based verification.
What you will learn:
- How to enroll authentication factors using WorkOS
- How to create authentication challenges (e.g., send an SMS code)
- How to verify MFA challenges
- How to store and manage factor and challenge IDs
- Best practices for integrating with your existing auth flow
Let’s dive right in!
Prerequisites
- A WorkOS account
- Your WORKOS_API_KEY and WORKOS_CLIENT_ID
- A server-side application in Node.js (or similar; we’ll use Node.js in examples). If you use a different language, refer to our MFA docs for code samples in Python, Go, Ruby, and more.
Step 1: Install the WorkOS SDK
For Node.js, install the WorkOS SDK using either npm or Yarn:
Step 2: Configure the WorkOS SDK
To make calls to WorkOS, you must authenticate using the WorkOS API key and client ID. Copy these values from the WorkOS dashboard.
.webp)
Store the values as managed secrets and pass them to the SDK either as environment variables or directly in your app’s configuration.
!!For more info on how to handle secrets safely, see Best practices for secrets management.!!
Environment variables example:
Then, initialize the SDK in your app:
Step 3: Enroll an authentication factor
You can choose either TOTP (for apps like Google Authenticator) or SMS.
We recommend using TOTP since SMS is not a secure MFA method.
Option A: TOTP
qr_code
is a base64 data URI for rendering a QR image.secret
can be typed into authenticator apps manually.
Option B: SMS Enrollment
Save the factorId
in your user database for future verifications.
Step 4: Create an MFA challenge
Now that a factor is enrolled, challenge it when the user signs in.
If you are using SMS, you can set a custom SMS message.
Step 5: Verify the MFA challenge
After the user submits their code:
If the challenge is successfully verified valid
will return true
. Otherwise it will return false
and another verification attempt must be made.
Error handling
Already verified error
If a challenge was already successfully verified, it cannot be used a second time. If further verification is needed in your application, create a new challenge.
Expired error
For SMS authentication factors, challenges are only available for verification for 10 minutes. After that they are expired and cannot be verified.
Integrating with your auth flow
Here’s how this might look in a simple login flow:
- User logs in with email/password
- Server checks if user has a stored factorId
- If yes:
- Challenge the factor
- Prompt user for code
- Verify the challenge
- If no:
- Enroll user in MFA (TOTP or SMS)
- Prompt for initial setup
- Save the factor ID
Security best practices for MFA
- Deprioritize SMS as a primary factor: Prefer TOTP (e.g. via Google Authenticator, Authy, 1Password, etc.) as the default MFA option. Offer SMS as a fallback only if necessary. Problems with SMS MFA:
- Vulnerable to SIM-swapping: Attackers can hijack SMS-based MFA by socially engineering mobile carriers.
- Susceptible to phishing and malware: SMS codes can be intercepted via insecure networks or malicious apps.
- No end-to-end encryption: SMS is not a secure transport channel.
- Enforce time-bound challenges:
- Challenge tokens (e.g., SMS or TOTP) should have short lifetimes — WorkOS defaults to 10 minutes for SMS.
- Consider implementing rate limiting and cooldown periods for repeated verification attempts.
- One-time use only:
- Never allow reuse of a successful MFA challenge.
- After a successful verification, the challenge ID should be marked as consumed or invalidated. WorkOS enforces this already — lean on that logic.
- Audit and log MFA events:
- Log all MFA enrollment, challenge, and verification attempts.
- Include metadata like IP address, user agent, timestamp, and MFA method used.
- Monitor for anomalies such as multiple failed challenges or enrollment from new devices.
- Rotate and re-enroll MFA devices:
- Provide mechanisms for users to update, re-enroll, or reset their MFA devices securely.
- If suspicious activity is detected, force re-enrollment or invalidate existing factors.
- Mitigate MFA fatigue:
- Limit MFA prompts for low-risk sessions (e.g., within the same device/browser).
- But always re-challenge on high-risk actions such as:
- Changing email or password
- Accessing admin dashboards
- Viewing sensitive data (PII, payment info)
- Regularly test and monitor MFA enforcement:
- Periodically test that MFA is enforced where expected.
- Run integration tests to verify that challenges, expirations, and verifications behave correctly.
- Secure secrets:
- Never expose the TOTP secret or SMS challenge codes outside of controlled flows.
- Always store factorId and challengeId securely.
- Store secrets securely (e.g., encrypted at rest, never in logs).
- Use HTTPS when displaying the qr_code or accepting user input.
- Limit retry attempts for verification to prevent brute-force.