To integrate Duo Security MFA with Bolt.new, create a Web SDK application in the Duo Admin Panel, get your client ID, client secret, and API hostname, then implement the Universal Prompt redirect flow in Next.js API routes. Duo's OAuth-based Universal Prompt requires a deployed redirect URL — the full MFA flow cannot be tested in Bolt's WebContainer preview. For simpler TOTP-based MFA without a third party, consider Supabase Auth's built-in MFA.
Adding Duo MFA to Your Bolt.new App's Login Flow
Duo Security is the leading enterprise MFA solution, protecting millions of users at companies like Cisco, Lyft, and Toyota. It adds a second authentication factor — a push notification to the Duo Mobile app, a TOTP code from any authenticator, a hardware security key, or biometric verification — after a user correctly enters their password. For Bolt.new apps serving business or security-conscious users, Duo integration signals enterprise-grade security.
Duo's modern integration method is the Universal Prompt, which uses OAuth 2.0 with PKCE. The flow works like any OAuth authorization: after password verification, your server redirects the user to Duo's hosted challenge page, the user completes their MFA verification, and Duo redirects back to your redirect_uri with a code. Your server exchanges that code (plus the PKCE verifier) for an ID token that confirms the user successfully passed MFA. This design means you never handle or store MFA secrets — Duo manages all the device enrollment and challenge logic.
The critical constraint for Bolt.new is that the Universal Prompt requires a stable, registered redirect_uri. You must register an exact URL in the Duo Admin Panel — the dynamic, ephemeral URL of Bolt's WebContainer preview cannot be registered. The pattern for Bolt development is to build all the API route logic in Bolt, deploy to Netlify or Bolt Cloud, register your deployed URL in Duo, and test the full MFA flow on the deployed site. For applications that need MFA but do not need Duo's enterprise features specifically, Supabase Auth includes built-in TOTP that works without any redirect flow and can be tested in the Bolt preview.
Integration method
Duo Security's Universal Prompt uses an OAuth 2.0 PKCE flow: after a user authenticates with their password, you redirect them to Duo's hosted challenge page, they complete MFA (push, code, biometric), and Duo redirects back to your server with an authorization code you exchange for a result token. The redirect callback requires a deployed URL — Bolt's WebContainer preview URL cannot be registered as a valid Duo redirect URI. Build the API routes in Bolt, then deploy and test the full flow on your deployed site.
Prerequisites
- A Duo Security account — free trial available at duo.com (requires business email)
- Access to the Duo Admin Panel to create a Web SDK application
- A deployed Bolt.new app URL (Netlify or Bolt Cloud) to register as the Duo redirect URI
- A Bolt.new project using Next.js with an existing authentication system (email/password)
- Familiarity with OAuth 2.0 PKCE flow concepts — Duo's Universal Prompt follows standard OAuth patterns
Step-by-step guide
Create a Duo Web SDK Application
Create a Duo Web SDK Application
Log in to the Duo Admin Panel at admin.duosecurity.com with your Duo admin account. In the left sidebar, navigate to Applications → Protect an Application. Search for 'Web SDK' in the integration list and click 'Protect' next to it. This creates a new Duo Web SDK application and shows you the integration credentials. You will see three key values: Client ID (your integration key, also called ikey), Client Secret (your secret key, also called skey), and API Hostname (a unique Duo domain like api-XXXXXXXX.duosecurity.com). Copy all three — you will need them for your application's environment variables. In the application settings, find the 'Redirect URIs' section. For now, add a placeholder like https://your-app.netlify.app/api/auth/duo/callback — you will replace this with your actual deployed URL after deployment. Duo strictly enforces that the redirect_uri in authorization requests matches a registered URI exactly. Under 'Settings' for the application, you can optionally configure the display name (shown on the Duo prompt), allowed groups, and MFA policies. For a basic integration, the defaults are fine. Save the application. Add to your .env file: NEXT_PUBLIC_DUO_CLIENT_ID (safe for client-side code — it's a public client identifier), DUO_CLIENT_SECRET (server-side only), DUO_API_HOSTNAME (the api-XXXXXXXX.duosecurity.com domain), and DUO_REDIRECT_URI (your deployed callback URL).
Set up the project for Duo Security integration. Create a .env.local file with NEXT_PUBLIC_DUO_CLIENT_ID=your_client_id, DUO_CLIENT_SECRET=your_client_secret, DUO_API_HOSTNAME=api-xxxxxxxx.duosecurity.com, and DUO_REDIRECT_URI=https://your-app.netlify.app/api/auth/duo/callback. Install @duosecurity/duo_universal_nodejs (Duo's official Node.js SDK). Create a src/lib/duo.ts helper that initializes and exports a Duo Client object using the SDK.
Paste this in Bolt.new chat
1# .env.local2NEXT_PUBLIC_DUO_CLIENT_ID=your_duo_client_id3DUO_CLIENT_SECRET=your_duo_client_secret4DUO_API_HOSTNAME=api-xxxxxxxx.duosecurity.com5DUO_REDIRECT_URI=https://your-app.netlify.app/api/auth/duo/callbackPro tip: Duo's Client Secret (skey) gives full API access to your Duo application, including the ability to bypass MFA. Keep it strictly server-side. The Client ID (ikey) is a public identifier and is safe to include in client-side code or frontend configuration.
Expected result: A Duo Web SDK application exists in the Duo Admin Panel with your credentials noted. Your .env file has all four required variables. The placeholder redirect URI is registered in Duo.
Initialize the Duo Client and Build the Auth Start Route
Initialize the Duo Client and Build the Auth Start Route
Duo provides an official Node.js SDK (@duosecurity/duo_universal_nodejs) that handles the PKCE challenge generation, state management, and authorization URL construction. Install it via npm in your Bolt project. Create a src/lib/duo.ts helper that initializes the Duo Client with your credentials. The Duo Client constructor takes clientId, clientSecret, apiHost, and redirectUrl. Create a new instance using environment variables — this client is used by both API routes that handle the MFA flow. The auth start route (/api/auth/duo/start) is called after successful password authentication. It calls duo_client.generateAuthUrl() with the current user's username. This function internally generates the PKCE state and code verifier, stores the state in the session (or returns it for you to store), and returns the full authorization URL for redirecting to Duo. Store the PKCE state and the username in the user's session (or a short-lived cookie) before redirecting. You'll need these in the callback to verify the response. The state prevents CSRF attacks — Duo includes it in the callback and you must verify it matches what you sent. IMPORTANT: This redirect cannot be tested in Bolt's WebContainer preview because Duo will redirect back to DUO_REDIRECT_URI, which must be a publicly accessible URL. Build the routes, deploy first, register the deployed URL in Duo, then test the full flow on the deployed site.
Install @duosecurity/duo_universal_nodejs via npm. Create src/lib/duo.ts that imports Client from @duosecurity/duo_universal_nodejs and exports a function createDuoClient() that initializes it with DUO_CLIENT_ID, DUO_CLIENT_SECRET, DUO_API_HOSTNAME, and DUO_REDIRECT_URI from process.env. Create /api/auth/duo/start/route.ts that accepts { username } in the POST body (the user who completed password auth), creates a Duo client, calls generateAuthUrl(username), stores the state and username in a session cookie, and returns the authorization URL for the client to redirect to.
Paste this in Bolt.new chat
1// src/lib/duo.ts2import { Client } from '@duosecurity/duo_universal_nodejs';34export function createDuoClient(): Client {5 const clientId = process.env.NEXT_PUBLIC_DUO_CLIENT_ID;6 const clientSecret = process.env.DUO_CLIENT_SECRET;7 const apiHost = process.env.DUO_API_HOSTNAME;8 const redirectUrl = process.env.DUO_REDIRECT_URI;910 if (!clientId || !clientSecret || !apiHost || !redirectUrl) {11 throw new Error('Duo Security credentials are not fully configured');12 }1314 return new Client({15 clientId,16 clientSecret,17 apiHost,18 redirectUrl,19 });20}2122// app/api/auth/duo/start/route.ts23import { NextRequest, NextResponse } from 'next/server';24import { createDuoClient } from '@/lib/duo';2526export async function POST(request: NextRequest) {27 const { username } = await request.json();28 if (!username) {29 return NextResponse.json({ error: 'username is required' }, { status: 400 });30 }3132 try {33 const duo = createDuoClient();3435 // Health check verifies Duo API is reachable before redirecting36 await duo.healthCheck();3738 const authUrl = await duo.createAuthUrl(username);3940 // Store state and username in session for callback verification41 const response = NextResponse.json({ authUrl });42 response.cookies.set('duo_pending_user', username, {43 httpOnly: true,44 secure: process.env.NODE_ENV === 'production',45 maxAge: 300, // 5 minutes to complete MFA46 sameSite: 'lax',47 });4849 return response;50 } catch (err: any) {51 return NextResponse.json({ error: err.message }, { status: 500 });52 }53}Pro tip: Call duo.healthCheck() before generating the auth URL to verify Duo's API is reachable. This prevents redirecting users to a Duo page that will fail — instead, you can show a meaningful error ('MFA service temporarily unavailable, please try again') and allow fallback to backup codes.
Expected result: The Duo Client initializes correctly with your credentials. The /api/auth/duo/start route generates a Duo authorization URL and stores the pending user in a cookie. Your login flow can call this endpoint after password verification to get the Duo redirect URL.
Build the Duo Callback Handler
Build the Duo Callback Handler
After the user completes MFA on Duo's Universal Prompt page, Duo redirects back to your DUO_REDIRECT_URI with two URL parameters: duo_code (the authorization code) and state (the state value from the auth URL). Your callback route exchanges these for an ID token using the Duo SDK's exchangeAuthorizationCodeFor2FAResult() function. The callback route retrieves the pending username from the session cookie, calls the Duo SDK to exchange the code, verifies the returned ID token's username matches the pending username (to prevent session fixation attacks), and if verification passes, grants the full authenticated session. If verification fails, redirect back to login with an error. The Duo ID token exchange must happen server-side — it uses your Client Secret. Never expose the client secret or exchange tokens in client-side code. The exchangeAuthorizationCodeFor2FAResult() function handles the PKCE verification, ID token signature validation, and claim verification internally. After successful MFA verification, clear the duo_pending_user cookie and set a full authentication session cookie (or update the existing session to mark MFA as completed). Redirect the user to their intended destination — the original page they were trying to access before being sent to MFA.
Create /api/auth/duo/callback/route.ts that handles the GET request from Duo's redirect. Extract duo_code and state from query parameters. Read the duo_pending_user from the cookie. Create a Duo client and call exchangeAuthorizationCodeFor2FAResult(duo_code, state, username). Verify the returned response's username matches the pending user. If everything checks out, set a full authenticated session cookie (mfa_verified=true, mfa_user=username, mfa_expires=15min), clear the duo_pending_user cookie, and redirect to /dashboard. If anything fails, redirect to /login?error=mfa_failed.
Paste this in Bolt.new chat
1// app/api/auth/duo/callback/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { createDuoClient } from '@/lib/duo';45export async function GET(request: NextRequest) {6 const duoCode = request.nextUrl.searchParams.get('duo_code');7 const state = request.nextUrl.searchParams.get('state');8 const pendingUser = request.cookies.get('duo_pending_user')?.value;910 // Validate required parameters11 if (!duoCode || !state || !pendingUser) {12 return NextResponse.redirect(new URL('/login?error=mfa_incomplete', request.url));13 }1415 try {16 const duo = createDuoClient();1718 // Exchange code for MFA result token19 // This validates PKCE, verifies the ID token signature, and checks claims20 const result = await duo.exchangeAuthorizationCodeFor2FAResult(21 duoCode,22 pendingUser // Duo verifies the username in the ID token matches this23 );2425 // Duo SDK throws if verification fails — reaching here means MFA passed26 const response = NextResponse.redirect(new URL('/dashboard', request.url));2728 // Grant full authenticated session29 response.cookies.set('mfa_verified', 'true', {30 httpOnly: true,31 secure: process.env.NODE_ENV === 'production',32 maxAge: 60 * 60 * 8, // 8 hour session33 sameSite: 'strict',34 });35 response.cookies.set('authenticated_user', pendingUser, {36 httpOnly: true,37 secure: process.env.NODE_ENV === 'production',38 maxAge: 60 * 60 * 8,39 sameSite: 'strict',40 });4142 // Clear the pending MFA cookie43 response.cookies.delete('duo_pending_user');4445 return response;46 } catch (err: any) {47 console.error('Duo MFA verification failed:', err.message);48 const response = NextResponse.redirect(49 new URL('/login?error=mfa_failed', request.url)50 );51 response.cookies.delete('duo_pending_user');52 return response;53 }54}Pro tip: The Duo SDK's exchangeAuthorizationCodeFor2FAResult() throws an error if MFA verification fails — you do not need to manually check a boolean result. Wrap in try-catch and treat any exception as a failed MFA attempt that should redirect to login.
Expected result: The callback handler exchanges the Duo code for a verified MFA result. Successful verification grants a full authenticated session. Failed verification (user denied, wrong code, timeout) redirects to login with an error message. The full flow works on your deployed site.
Wire MFA into Your Login Flow and Deploy
Wire MFA into Your Login Flow and Deploy
With the Duo API routes in place, connect them to your existing login flow. The typical pattern is: (1) user submits email and password, (2) your existing auth route verifies credentials, (3) if credentials are correct AND the user requires MFA, call /api/auth/duo/start to get the Duo auth URL, (4) redirect the user to the Duo URL instead of granting a session immediately, (5) after Duo callback, grant the session. For Supabase Auth users, the typical integration point is in the login page after supabase.auth.signInWithPassword() succeeds. Check if the authenticated user has MFA required (check a custom field like mfa_required in their profile), and if so, initiate the Duo flow before allowing access to protected pages. Deploy your app: publish to Netlify or Bolt Cloud. After getting your deployed URL (e.g., https://your-app.netlify.app), go back to the Duo Admin Panel → your Web SDK application → Settings → Redirect URIs. Replace the placeholder with your exact deployed callback URL: https://your-app.netlify.app/api/auth/duo/callback. The redirect URI must match character-for-character with DUO_REDIRECT_URI in your production environment variables. Set all four environment variables in your deployment platform: NEXT_PUBLIC_DUO_CLIENT_ID, DUO_CLIENT_SECRET, DUO_API_HOSTNAME, and DUO_REDIRECT_URI. Trigger a new deployment after adding them. Test the full login → Duo prompt → callback → dashboard flow on your deployed site.
Wire Duo MFA into the login flow. After a successful Supabase login (supabase.auth.signInWithPassword succeeds), call POST /api/auth/duo/start with the user's email as the username. Take the returned authUrl and use window.location.href to redirect the user to Duo. Add a 'Verifying with Duo Security...' loading state between password success and Duo redirect. On the login page, check for ?error=mfa_failed in the URL and show an error message 'MFA verification failed. Please try again.' if present. Also add a helper middleware at src/middleware.ts that checks for the mfa_verified cookie on /dashboard routes and redirects to /login if missing.
Paste this in Bolt.new chat
1// src/middleware.ts — protect routes requiring MFA2import { NextRequest, NextResponse } from 'next/server';34export function middleware(request: NextRequest) {5 const mfaVerified = request.cookies.get('mfa_verified')?.value;6 const authenticatedUser = request.cookies.get('authenticated_user')?.value;78 // Protect /dashboard routes — require completed MFA9 if (request.nextUrl.pathname.startsWith('/dashboard')) {10 if (!mfaVerified || !authenticatedUser) {11 return NextResponse.redirect(new URL('/login', request.url));12 }13 }1415 return NextResponse.next();16}1718export const config = {19 matcher: ['/dashboard/:path*'],20};Pro tip: Test the full MFA flow with Duo's development-mode phone setup: install Duo Mobile on your phone, enroll a test account in the Duo Admin Panel (Users → Add User → Enroll Device), and verify the push notification arrives during testing on your deployed site.
Expected result: The complete MFA flow works on your deployed site: login with password → redirect to Duo → push notification or code → callback → dashboard access. Unauthenticated or MFA-incomplete users are redirected to login by the middleware.
Common use cases
Two-Factor Authentication for Admin Panel
Protect an admin dashboard with Duo MFA as a mandatory second step. After admins enter their password successfully, trigger the Duo Universal Prompt before granting access to sensitive management functions. Duo's push notification makes this low-friction for the admin while providing strong security.
Add Duo MFA to the admin login flow. After a user enters their email and password correctly, check if they are in the 'admin' role. If they are, initiate the Duo Universal Prompt instead of granting immediate access — redirect to /api/auth/duo/start which builds the Duo authorization URL. After Duo MFA is completed, the callback at /api/auth/duo/callback verifies the result token and grants the admin session. Non-admin users bypass Duo and get immediate access. Store NEXT_PUBLIC_DUO_CLIENT_ID, DUO_CLIENT_SECRET, and DUO_API_HOSTNAME in .env.
Copy this prompt to try it in Bolt.new
Step-Up Authentication for Sensitive Actions
Trigger Duo MFA only when users attempt high-risk actions like transferring funds, exporting data, or changing account settings — not on every login. This step-up pattern reduces MFA friction for normal use while requiring verification for sensitive operations.
Implement step-up MFA for sensitive actions using Duo. Create a context that tracks whether the current session has completed Duo verification in the last 15 minutes. When a user tries to perform a sensitive action (delete account, export data, change email), if they have not recently verified with Duo, redirect to /api/auth/duo/start with a returnUrl parameter. After successful Duo verification, redirect to the returnUrl and allow the action. Store the Duo verification timestamp in the session.
Copy this prompt to try it in Bolt.new
Compliance-Driven MFA for All Users
Enforce Duo MFA for every user on every login to meet compliance requirements (SOC 2, HIPAA, PCI-DSS). Every successful password authentication triggers the Duo Universal Prompt, and the application session is only granted after Duo verification succeeds.
Enforce Duo MFA for all users on every login. After any successful password authentication, always trigger /api/auth/duo/start regardless of user role. Store a pending_duo_user_id in the session before the redirect. After Duo callback completes successfully, verify the ID token and grant the full authenticated session. If the user closes Duo or cancels verification, return them to the login page with an error. Log all MFA attempts (success, failure, timestamp, user ID) to a compliance audit table in the database.
Copy this prompt to try it in Bolt.new
Troubleshooting
Duo Admin Panel shows 'Invalid redirect_uri' when initiating the Universal Prompt
Cause: The redirect_uri in the authorization request does not exactly match a URI registered in the Duo application's settings. Duo performs exact string matching including protocol, domain, port, and path.
Solution: In the Duo Admin Panel, open your Web SDK application → Settings → Redirect URIs. Confirm the registered URI matches your DUO_REDIRECT_URI environment variable exactly: https://your-app.netlify.app/api/auth/duo/callback. A missing trailing slash, http vs https mismatch, or different path causes rejection.
Duo redirect works but callback throws 'State mismatch' or 'Invalid state' error
Cause: The state value generated by createAuthUrl() was not persisted correctly between the start route and the callback, or the session cookie storing the state expired before the user completed MFA.
Solution: Increase the maxAge of the duo_pending_user session cookie to at least 300 seconds (5 minutes). Ensure the cookie is set with sameSite: 'lax' so it is sent on the Duo redirect back. If users are completing Duo verification on a different device (common with push notifications), session state must be stored in a database rather than a cookie.
1response.cookies.set('duo_pending_user', username, {2 httpOnly: true,3 secure: true,4 maxAge: 300, // 5 minutes — ensure this is long enough for user to complete MFA5 sameSite: 'lax', // 'lax' allows the cookie to be sent on cross-site redirects6});Duo health check fails with 'Cannot connect to Duo API' during development
Cause: The DUO_API_HOSTNAME is incorrect or the Duo application is not properly configured. In Bolt's WebContainer, outbound HTTPS calls to external services work, so this is typically a credential issue.
Solution: Verify DUO_API_HOSTNAME is the exact api-XXXXXXXX.duosecurity.com subdomain from your Duo application settings. This hostname is unique to your Duo account and application. Log the environment variable value in the API route to confirm it is being read correctly.
Best practices
- Always call duo.healthCheck() before initiating the MFA flow — if Duo's API is down, you can show a meaningful error and optionally allow emergency bypass codes rather than leaving users locked out.
- Store Duo's pending state in a server-side session or database for multi-device MFA flows (push notifications) — browser cookies are device-specific and will fail if the user approves Duo on a different device.
- Set a short maxAge (5 minutes) on the pending MFA cookie — if a user abandons the Duo prompt and returns later, they should start fresh from the login page rather than completing a stale MFA challenge.
- Register both your production URL and a staging/preview URL as redirect URIs in Duo — this allows testing on different environments without changing the registered URI each time.
- Log all Duo MFA events (success, failure, user, timestamp) to a compliance audit table — MFA logs are required evidence for SOC 2 and ISO 27001 audits.
- For simpler TOTP MFA without enterprise requirements, use Supabase Auth's built-in MFA — it supports authenticator app TOTP without any redirect flow, works in Bolt's preview, and requires no third-party account.
- Implement MFA bypass codes (stored securely, hashed in the database) as a recovery mechanism for users who lose their Duo device — never leave users permanently locked out.
Alternatives
Okta provides a full identity platform with SSO, directory sync, and MFA, better for enterprise apps needing complete identity management beyond MFA alone.
Auth0 includes built-in MFA (TOTP, SMS, push) as part of its identity platform, with less enterprise overhead than Duo for applications that already use Auth0 for authentication.
LastPass offers MFA as part of a password management suite, less commonly used as a standalone developer API compared to Duo's developer-focused SDK.
Firebase Auth includes phone number MFA and TOTP built-in without any redirect flow, simpler to integrate than Duo for apps already using Firebase.
Frequently asked questions
Can I test Duo MFA in Bolt.new without deploying?
Partially. The API route code (credential setup, Duo client initialization, health check) can be built and verified in Bolt's preview. However, the full Duo Universal Prompt flow — which requires Duo to redirect back to your redirect_uri — cannot complete in the WebContainer because Bolt's preview URL is dynamic and cannot be registered as a valid Duo redirect URI. Deploy to Netlify or Bolt Cloud, register your deployed URL in Duo, then test the complete MFA flow.
Is Duo Security free to use for small apps?
Duo offers a free tier for up to 10 users, which is useful for testing and small internal apps. Beyond 10 users, Duo requires a paid plan starting at the Essentials tier. Check duo.com/pricing for current pricing — Duo's plans are per-user per-month with volume discounts for larger deployments.
Should I use Duo or Supabase Auth's built-in MFA for my Bolt.new app?
For consumer apps, startups, or apps where users are not Duo-enrolled, use Supabase Auth's built-in TOTP MFA — it works in Bolt's preview, requires no third-party account setup for users, and supports standard authenticator apps (Google Authenticator, 1Password, etc.). Use Duo when you're building for enterprise customers who already use Duo in their organization, need push notification MFA specifically, or require Duo's compliance and audit reporting features.
Does Duo MFA work with social login (Google, GitHub) in addition to email/password?
Yes — Duo MFA is a second factor that works after any first-factor authentication, including OAuth social login. In your callback from Google or GitHub OAuth, after confirming the user's identity, initiate the Duo prompt as a mandatory second step before granting the application session. Store the OAuth user identity in the pending session before redirecting to Duo.
What happens if a user loses their Duo device?
Implement backup codes as a recovery mechanism: generate a set of one-time codes during enrollment, hash and store them in your database, and present them as a fallback option on the Duo challenge page. Duo also supports bypass codes in the Admin Panel for emergency access. Without recovery options, users who lose their Duo device can be permanently locked out — always implement at least one recovery path.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation