Add Duo Security MFA to your Lovable app by creating a Supabase Edge Function that calls Duo's Auth API to trigger push notifications, SMS codes, or hardware token verification as a second factor on top of Supabase Auth. Store your Duo integration key, secret key, and API hostname in Cloud → Secrets. Duo is MFA-only — it adds a second factor after the user has already authenticated with Supabase Auth, unlike Auth0 which is a complete identity provider.
Add Duo push-based MFA as a second factor to your Lovable app's login flow
Multi-factor authentication (MFA) reduces account compromise risk by requiring a second proof of identity — something the user has (their phone), not just something they know (their password). Duo Security specializes in this second factor. Rather than replacing your authentication system, Duo adds on top of it: users still log in with their existing credentials, then Duo prompts them on their enrolled device (mobile app push, SMS, phone call, or hardware token) to confirm the login.
In a Lovable app built on Supabase Auth, the integration flow is: user enters email and password → Supabase Auth validates credentials → if valid, trigger Duo second factor → user approves push notification on their phone → Edge Function verifies Duo approval → set a 'mfa_verified' flag in the user's Supabase profile → allow access to protected resources. Row Level Security policies check the mfa_verified state before allowing access to sensitive data.
Duo differs importantly from Auth0 in scope. Auth0 is a complete identity provider — it handles user registration, login, social OAuth, password reset, and MFA in one package. Duo is MFA only. It does not manage user accounts, handle login forms, or issue identity tokens. If you already have Supabase Auth handling primary authentication and want to add enterprise-grade MFA with Duo's familiar push notification UX, this integration is the right approach. For organizations using Duo across their entire enterprise app stack, it provides a consistent MFA experience with centralized device enrollment and audit logging.
Integration method
Duo Security integrates with Lovable as a second authentication factor layered on top of Supabase Auth. After a user signs in with email/password or social login through Supabase, an Edge Function calls Duo's Auth API to trigger a push notification or SMS code to the user's enrolled Duo device. The user approves the push or enters the code, and the Edge Function verifies the result before issuing a session-level MFA confirmation stored in the user's Supabase profile. Duo integration credentials (ikey, skey, api_hostname) are stored in Cloud → Secrets.
Prerequisites
- A Duo Security account at duo.com (free for up to 10 users)
- A Duo Auth API application created in the Duo Admin Panel (Admin Panel → Applications → Protect an Application → Auth API)
- Your Duo integration key (ikey), secret key (skey), and API hostname from the Auth API application
- Your users enrolled in Duo with an active device (Duo Mobile app or SMS-capable phone)
- A Lovable account with Supabase Auth enabled for primary authentication
Step-by-step guide
Create a Duo Auth API application and store credentials in Cloud → Secrets
Create a Duo Auth API application and store credentials in Cloud → Secrets
Duo integrations are configured in the Duo Admin Panel (admin.duosecurity.com). Each application you protect gets its own integration key and secret key, allowing Duo to attribute MFA activity to specific applications and apply different policies per app. To create the Auth API application: Log in to the Duo Admin Panel. In the left navigation, click 'Applications'. Click 'Protect an Application'. Search for 'Auth API' in the application list. Click 'Protect' next to Auth API. The application is created immediately and you see the integration credentials page showing: 1. Integration key (ikey) — like DIXXXXXXXXXXXXXXXXXX 2. Secret key (skey) — 40-character hex string 3. API hostname — like api-XXXXXXXX.duosecurity.com Also optionally configure a name for the application (e.g., 'My Lovable App') and any Duo policies (which authentication methods to allow, device trust requirements, etc.). In Lovable, click '+' next to Preview to open the Cloud panel, then click 'Secrets'. Add: - DUO_IKEY — your Duo integration key - DUO_SKEY — your Duo secret key - DUO_API_HOST — your Duo API hostname (e.g., api-XXXXXXXX.duosecurity.com) Duo credentials grant the ability to trigger MFA for any enrolled user. Treat the secret key with the same care as a password — it allows calling Duo Auth API on behalf of your application. Lovable's security blocks approximately 1,200 hardcoded keys daily, and this is exactly the type of credential that must be in the Secrets panel, never in code.
Pro tip: Enable the 'New user policy' in your Duo application to control what happens when an unrecognized user tries to authenticate — 'Allow access (without 2FA)' for gradual rollout, or 'Deny access' for strict MFA enforcement from day one.
Expected result: DUO_IKEY, DUO_SKEY, and DUO_API_HOST are stored in Cloud → Secrets. The Duo application shows as 'Active' in the Duo Admin Panel.
Create the Duo HMAC-SHA1 signing helper
Create the Duo HMAC-SHA1 signing helper
Duo's Auth API uses a custom HMAC-SHA1 request signing scheme rather than simple API key headers. Every request to the Duo API must include an Authorization header containing an HMAC-SHA1 signature of the canonical request string, signed with the secret key. This signing scheme prevents replay attacks and verifies request integrity. The canonical request string is built from: timestamp, HTTP method (uppercase), API hostname (lowercase), API path, and sorted URL-encoded parameters — joined by newlines. The HMAC-SHA1 of this string (using skey as the key) is hex-encoded, then combined with the integration key in HTTP Basic Auth format (ikey as username, HMAC as password). Deno's Web Crypto API provides HMAC-SHA1 through crypto.subtle.sign(). The implementation below handles the signature calculation. This is the most complex part of the Duo integration — all API calls to Duo (preauth, auth, check) use this same signing logic.
Create a Supabase Edge Function at supabase/functions/duo-auth/index.ts that integrates with the Duo Auth API. Implement HMAC-SHA1 request signing using Deno's Web Crypto API (DUO_IKEY, DUO_SKEY, DUO_API_HOST from Deno.env.get). Support three operations: 'preauth' to check if a user is enrolled and get available factors, 'auth' to send a push or SMS to the user, and 'auth_status' to poll for the push approval result. Accept a POST request with 'operation', 'username', and optionally 'factor' and 'txid'. Include CORS headers.
Paste this in Lovable chat
1// supabase/functions/duo-auth/index.ts2const corsHeaders = {3 'Access-Control-Allow-Origin': '*',4 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',5};67async function hmacSha1(key: string, message: string): Promise<string> {8 const encoder = new TextEncoder();9 const cryptoKey = await crypto.subtle.importKey(10 'raw', encoder.encode(key),11 { name: 'HMAC', hash: 'SHA-1' },12 false, ['sign']13 );14 const signature = await crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(message));15 return Array.from(new Uint8Array(signature)).map(b => b.toString(16).padStart(2, '0')).join('');16}1718async function duoRequest(19 method: string,20 path: string,21 params: Record<string, string>22): Promise<unknown> {23 const ikey = Deno.env.get('DUO_IKEY')!;24 const skey = Deno.env.get('DUO_SKEY')!;25 const apiHost = Deno.env.get('DUO_API_HOST')!;2627 const now = new Date().toUTCString();28 const sortedParams = Object.keys(params).sort().map(k =>29 `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`30 ).join('&');3132 const canon = [now, method.toUpperCase(), apiHost.toLowerCase(), path, sortedParams].join('\n');33 const sig = await hmacSha1(skey, canon);34 const authHeader = 'Basic ' + btoa(`${ikey}:${sig}`);3536 const url = method === 'GET'37 ? `https://${apiHost}${path}?${sortedParams}`38 : `https://${apiHost}${path}`;3940 const fetchOptions: RequestInit = {41 method,42 headers: { 'Authorization': authHeader, 'Date': now, 'Content-Type': 'application/x-www-form-urlencoded' },43 };44 if (method === 'POST') fetchOptions.body = sortedParams;4546 const resp = await fetch(url, fetchOptions);47 return resp.json();48}4950Deno.serve(async (req) => {51 if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders });5253 try {54 const { operation, username, factor, txid, passcode } = await req.json() as {55 operation: string; username?: string; factor?: string; txid?: string; passcode?: string;56 };5758 let result: unknown;5960 if (operation === 'preauth') {61 result = await duoRequest('POST', '/auth/v2/preauth', { username: username! });62 } else if (operation === 'auth') {63 const params: Record<string, string> = { username: username!, factor: factor || 'push', device: 'auto' };64 if (factor === 'passcode' && passcode) params.passcode = passcode;65 result = await duoRequest('POST', '/auth/v2/auth', params);66 } else if (operation === 'auth_status') {67 result = await duoRequest('GET', '/auth/v2/auth_status', { txid: txid! });68 } else {69 return new Response(JSON.stringify({ error: 'Invalid operation. Use: preauth, auth, auth_status' }), {70 status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' },71 });72 }7374 return new Response(JSON.stringify(result), {75 headers: { ...corsHeaders, 'Content-Type': 'application/json' },76 });77 } catch (error) {78 console.error('duo-auth error:', error);79 return new Response(JSON.stringify({ error: String(error) }), {80 status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' },81 });82 }83});Pro tip: The Duo preauth call returns 'enroll' for users who have not set up Duo yet — handle this response in your frontend by redirecting new users to a Duo enrollment link rather than showing an error.
Expected result: The duo-auth Edge Function deploys. A test preauth call with a Duo-enrolled username returns result.stat = 'OK' and result.response.result = 'auth' (meaning the user is enrolled and can authenticate). Cloud → Logs confirms 200 responses from the Duo API.
Build the MFA verification flow in your Lovable app
Build the MFA verification flow in your Lovable app
With the Duo Edge Function deployed, build the authentication flow in Lovable. The MFA flow happens after Supabase Auth primary login succeeds. The sequence is: 1. User completes Supabase Auth login (email/password or social) 2. Your app calls the duo-auth Edge Function with operation='preauth' to verify the user is enrolled 3. App calls duo-auth with operation='auth' and factor='push' — this sends a push to the user's phone 4. The Edge Function returns a txid (transaction ID) 5. The frontend polls duo-auth with operation='auth_status' and the txid every 2 seconds 6. When auth_status returns result='allow', set mfa_verified = true in the user's Supabase profile 7. Redirect to the protected route For SMS factor: call auth with factor='sms' to send an SMS code, then call auth again with factor='passcode' and the passcode value from the user's input to verify. The mfa_verified state in Supabase is a timestamp column in a user_security table. A separate RLS policy on sensitive tables checks that user_security.mfa_verified is within the last 12 hours (or whatever your session timeout is). This way, Supabase auth session and Duo MFA state are tracked independently. For organizations with complex Duo enrollment workflows, multi-device management, or Duo bypass codes for device recovery, RapidDev's team can help design a complete enterprise MFA architecture on Lovable.
Build a Duo MFA verification page at /verify-mfa. When the page loads, call the duo-auth Edge Function with operation='preauth' and the current user's email. If result='auth', automatically call auth with factor='push' to send a Duo push. Show 'Check your Duo app — push notification sent' with a spinner. Poll the duo-auth Edge Function with auth_status every 2 seconds using the returned txid. When result='allow', update the user_mfa_status table in Supabase to set mfa_verified_at = now() for the current user, then redirect to the originally requested page. If the push is denied or times out, show an error with a 'Resend push' button.
Paste this in Lovable chat
Pro tip: Add a 5-minute expiry check on mfa_verified_at in your RLS policies. Requiring re-verification after session expiry prevents a user from staying MFA-verified indefinitely if their computer is left unattended.
Expected result: The /verify-mfa page sends a Duo push to the enrolled user's phone. When the user approves in the Duo app, the frontend detects the successful auth_status, updates the MFA state in Supabase, and redirects to the protected route.
Common use cases
Require Duo push approval before accessing sensitive admin pages
Your Lovable app has an admin dashboard that accesses sensitive customer data. After admin users log in with Supabase Auth, they must approve a Duo push notification on their enrolled phone before the admin route is accessible. The MFA state is checked by an Edge Function and stored as a time-limited session flag in the user's Supabase profile. Admin data RLS policies require mfa_verified = true.
Build an admin route guard that requires Duo MFA. After an admin user logs in with Supabase Auth, redirect them to a /verify-mfa page before the admin dashboard. This page calls the duo-auth-start Edge Function which sends a Duo push to the user's enrolled device. Show 'Waiting for Duo approval...' with a loading indicator. When the duo-auth-verify Edge Function confirms approval, set mfa_verified = true in the user_security table and redirect to /admin. If the push is denied or times out, show an error and redirect to login.
Copy this prompt to try it in Lovable
SMS passcode verification for high-value transactions
A financial or e-commerce app requires additional verification before processing payments or withdrawals above a threshold. The transaction flow calls the Duo SMS endpoint to send a one-time passcode to the user's registered phone. The user enters the 6-digit code in a modal, the Edge Function verifies it with Duo, and the transaction proceeds only after successful verification. This step-up authentication pattern protects high-risk actions without requiring MFA on every login.
Add step-up MFA for transactions over $500. When a user initiates a large transfer, show a verification modal that calls the duo-auth-sms Edge Function to send an SMS code to their Duo-enrolled phone. Add a code input field in the modal. On submit, call duo-auth-verify with the passcode. If verified, proceed with the transaction and log the mfa_verification_id in the transactions table. If denied, show an error and cancel the transaction.
Copy this prompt to try it in Lovable
New device login confirmation via Duo push
When a user logs in from an unrecognized IP address or browser fingerprint, trigger Duo push verification as a suspicious activity gate. The app detects login from a new location, stores a pending verification record in Supabase, sends a Duo push with a contextual message (login from new location, time, and IP), and the user either approves or denies. Denials trigger an account security alert email.
Add new device detection to the login flow. After Supabase Auth login, compare the current IP and browser fingerprint against the user's known_devices table. If no match, call the duo-auth-start Edge Function with context message 'Login from new location: {ip}'. Show a 'Verify this login' page. If the user approves in Duo, add the device to known_devices and proceed. If denied, sign out the user and send a security alert email via Supabase Edge Function.
Copy this prompt to try it in Lovable
Troubleshooting
Duo API returns '40301 Invalid signature' error
Cause: The HMAC-SHA1 signature is computed incorrectly — the most common causes are: the system clock is more than 5 minutes off from Duo's servers (Duo has strict time-based replay prevention), the canonical string parameters are not sorted alphabetically, or there are extra whitespace characters in the DUO_SKEY secret.
Solution: Verify the canonical string construction: parameters must be sorted alphabetically by key name, URL-encoded with uppercase %XX encoding, and joined with '&'. The canonical string lines must be joined with literal newline characters. Also check that DUO_SKEY in Cloud → Secrets has no leading/trailing whitespace. For clock skew issues, Duo will reject requests where the Date header differs from their clock by more than 300 seconds.
Duo preauth returns 'result: enroll' for the user
Cause: The user is not enrolled in Duo — they have not set up the Duo Mobile app or added a device to their Duo account.
Solution: Handle the 'enroll' result in your frontend by displaying a Duo enrollment link. Duo provides a self-service enrollment URL in the preauth response's 'enroll_portal_url' field. Show this link to users who are not yet enrolled, explaining that they need to set up Duo before accessing the protected feature. Do not show an authentication error — the 'enroll' status is expected for new users.
1// In frontend — handle Duo preauth 'enroll' result2if (preauthResult.response.result === 'enroll') {3 const enrollUrl = preauthResult.response.enroll_portal_url;4 // Redirect to enrollment or show enrollment instructions5 window.open(enrollUrl, '_blank');6}auth_status polling returns 'waiting' indefinitely — push notification never resolves
Cause: The user dismissed the push, the Duo mobile app is not responding (no internet connection on the phone), or the push timed out (Duo pushes expire after 60 seconds).
Solution: Implement a polling timeout in the frontend — stop polling after 60 seconds and show an error with a 'Resend push' button. Also implement an exponential backoff starting at 2 seconds between polls to avoid hammering the Duo API. When the timeout is reached, call duo-auth with operation='auth' again to send a fresh push notification.
Edge Function returns 'Secret DUO_IKEY not found' or similar Deno.env error
Cause: The Duo secrets were not saved correctly in Cloud → Secrets, or the secret names in code do not match the names stored in Secrets.
Solution: Open Cloud → Secrets in the Lovable panel and verify DUO_IKEY, DUO_SKEY, and DUO_API_HOST all exist with values. Secret names are case-sensitive and must match exactly — DUO_IKEY is different from duo_ikey. After verifying secrets exist, test the Edge Function again — secrets take effect immediately without needing redeployment.
Best practices
- Track Duo MFA verification state in a separate Supabase user_security table with a mfa_verified_at timestamp — do not rely solely on Supabase Auth session state, as MFA should be re-verified after a configurable time window.
- Enforce MFA verification with Supabase RLS policies that check mfa_verified_at IS NOT NULL AND mfa_verified_at > NOW() - INTERVAL '12 hours' — this ensures protected data is inaccessible even to authenticated users who have not completed MFA.
- Handle the Duo 'enroll' preauth result gracefully — provide users with the enrollment portal URL rather than showing an error, since many first-time users will not have Duo set up.
- Implement polling timeout for Duo push status — stop polling after 60 seconds and provide a 'Resend push' button, since push notifications expire and users may miss them.
- Use the canonical username consistently — if your Supabase users authenticate with an email address, always pass that same email as the Duo username. Inconsistent username formats (email in some places, user ID in others) cause Duo to create duplicate unlinked user records.
- Store Duo bypass codes for device recovery in a secure location and document the process — users who lose their Duo device cannot authenticate without a bypass code or admin override.
- Monitor Duo authentication activity in the Duo Admin Panel authentication logs — failed authentications and denied pushes can indicate brute force attempts or compromised account credentials that warrant security review.
Alternatives
Choose Auth0 if you want a complete identity provider that handles user registration, login, social OAuth, and built-in MFA in a single platform — Auth0 replaces Supabase Auth rather than adding to it.
Choose Okta if your organization needs enterprise SSO and workforce identity management alongside MFA — Okta covers both employee login and customer-facing authentication at an enterprise scale.
Frequently asked questions
Does Duo replace Supabase Auth or work alongside it?
Duo works alongside Supabase Auth as a second factor — it does not replace primary authentication. Users still log in with their Supabase Auth credentials (email/password or social login). After the primary login succeeds, your app triggers a Duo second factor before granting access to protected resources. Supabase Auth manages identity and sessions; Duo manages MFA verification. Both are required for the full authentication flow.
What Duo authentication factors are supported in this integration?
The Duo Auth API supports push notifications (via the Duo Mobile app — tap Approve or Deny), SMS passcodes (Duo sends a 6-digit code to the user's phone by text), phone call (automated call asking user to press 1 to approve), and hardware tokens (TOTP codes from a Duo hardware token or third-party TOTP app). The 'push' factor is recommended for best user experience. The Edge Function supports all factors by setting the factor parameter in the auth call.
Is Duo free for small Lovable apps?
Duo offers a free plan for up to 10 users with core MFA features. For 11+ users, Duo Essentials starts at $3/user/month. Duo Advantage ($6/user/month) adds risk-based authentication and device health checks. For a small internal tool or admin panel protecting a handful of users, the free plan is sufficient. Check duo.com/pricing for current plan details.
How do I handle users who have lost their Duo device?
Duo provides bypass codes — one-time-use codes generated in the Duo Admin Panel that allow users to authenticate without their enrolled device. Generate a set of bypass codes for each user during enrollment and store them securely offline. For enterprise deployments, your Duo administrator can create bypass codes on demand for locked-out users. Also configure Duo's 'Remembered Devices' feature to reduce MFA friction on trusted devices while still protecting new device logins.
Can I require Duo MFA only for certain users or actions, not every login?
Yes. The most flexible approach is step-up authentication: only trigger Duo verification when users access high-privilege routes (admin pages, sensitive data) or perform high-risk actions (large transactions, configuration changes). Implement this by checking the mfa_verified_at timestamp only in the RLS policies protecting sensitive resources — standard pages with non-sensitive data do not require MFA verification. This reduces friction for everyday users while protecting critical operations.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation