How to Architect Secure Multi-Factor Authentication (MFA) Flows in Next.js 15 with React & TypeScript (2026)

Securing user accounts with Multi-Factor Authentication (MFA) is no longer an option but a necessity. As digital threats evolve, robust authentication mechanisms are paramount. With Next.js 15, React, and TypeScript, developers have powerful tools to build sophisticated and secure MFA flows. This guide demonstrates a production-ready pattern leveraging Next.js Middleware and Server Actions to architect a secure, code-first MFA system, ensuring your application meets modern security standards in 2026.

1. Initial Authentication with Server Actions & Secure Cookies

The journey begins with user authentication. We'll use a Server Action to handle login, issuing a secure, HttpOnly cookie containing a JSON Web Token (JWT). This token will include an mfa_verified flag, initially false if MFA is enabled for the user.

1.1. Login Form (Client Component)

A simple React client component handles user input and invokes the server action.

REACT COMPONENT
// app/login/page.tsx
'use client'; // This is a Client Component

import { useState } from 'react';
import { authenticate } from '@/app/lib/actions'; // Server Action import

interface AuthResult {
  success: boolean;
  message?: string;
  redirectTo?: string;
}

export default function LoginPage() {
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setLoading(true);
    setError(null);

    const formData = new FormData(event.currentTarget);
    const result: AuthResult = await authenticate(formData); // Call the server action

    if (!result.success) {
      setError(result.message || 'Authentication failed.');
    } else if (result.redirectTo) {
      window.location.href = result.redirectTo; // Client-side redirect
    }
    setLoading(false);
  };

  return (
    <div className="flex min-h-screen items-center justify-center bg-gray-100">
      <div className="w-full max-w-md rounded-lg bg-white p-8 shadow-md">
        <h1 className="mb-6 text-center text-3xl font-bold text-gray-900">Login</h1>
        <form onSubmit={handleSubmit} className="space-y-6">
          <div>
            <label htmlFor="email" className="block text-sm font-medium text-gray-700">Email</label>
            <input
              id="email"
              name="email"
              type="email"
              required
              className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
              placeholder="you@example.com"
            />
          </div>
          <div>
            <label htmlFor="password" className="block text-sm font-medium text-gray-700">Password</label>
            <input
              id="password"
              name="password"
              type="password"
              required
              className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
              placeholder="••••••••"
            />
          </div>
          {error && <p className="text-sm text-red-600">{error}</p>}
          <div>
            <button
              type="submit"
              disabled={loading}
              className="flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-50"
            >
              {loading ? 'Logging In...' : 'Login'}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

1.2. Authentication Server Action

This server action handles credentials verification, JWT generation, and setting the HttpOnly cookie. It leverages next/headers for cookie manipulation and next/navigation for server-side redirects.

REACT COMPONENT
// app/lib/actions.ts
'use server';

import { cookies } from 'next/headers';
import { SignJWT } from 'jose';
import { redirect } from 'next/navigation';

// Mock Database and environment variables
const MOCK_DB_USERS = [
  { id: 'user1', email: 'test@example.com', password: 'password123', mfaEnabled: true, mfaSecret: 'JBSWY3DPEHPK3PXP' },
  { id: 'user2', email: 'nomfa@example.com', password: 'password123', mfaEnabled: false },
];
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET || 'super_secret_jwt_key_that_is_at_least_32_bytes_long'); // Use a strong, env var secret

interface UserPayload {
  userId: string;
  email: string;
  mfaVerified: boolean;
  mfaEnabled: boolean;
}

export async function authenticate(formData: FormData): Promise<{ success: boolean; message?: string; redirectTo?: string }> {
  const email = formData.get('email')?.toString();
  const password = formData.get('password')?.toString();

  if (!email || !password) {
    return { success: false, message: 'Email and password are required.' };
  }

  // 1. Verify user credentials (replace with actual DB lookup and bcrypt compare)
  const user = MOCK_DB_USERS.find(u => u.email === email);
  if (!user || user.password !== password) { // In production, use bcrypt.compare
    return { success: false, message: 'Invalid credentials.' };
  }

  // 2. Create JWT payload
  const payload: UserPayload = {
    userId: user.id,
    email: user.email,
    mfaVerified: false, // Initially false
    mfaEnabled: user.mfaEnabled,
  };

  try {
    // 3. Sign JWT
    const token = await new SignJWT(payload)
      .setProtectedHeader({ alg: 'HS256' })
      .setIssuedAt()
      .setExpirationTime('1h') // Token expires in 1 hour
      .sign(JWT_SECRET);

    // 4. Set HttpOnly, Secure cookie
    cookies().set('auth_token', token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      maxAge: 60 * 60 * 24 * 7, // 1 week
      path: '/',
      sameSite: 'lax',
    });

    if (user.mfaEnabled) {
      redirect('/mfa-verify'); // Server-side redirect to MFA verification
    } else {
      redirect('/dashboard'); // Server-side redirect to dashboard
    }

  } catch (error) {
    console.error('Authentication failed:', error);
    return { success: false, message: 'An unexpected error occurred during login.' };
  }
}

// Example logout action
export async function logout() {
  cookies().delete('auth_token');
  redirect('/login');
}

2. Enforcing MFA with Next.js Middleware

Next.js Middleware is critical for protecting routes and enforcing authentication policies before a request reaches a page or API route. Here, we'll use it to check the auth_token and redirect users who are unauthenticated or require MFA verification.

REACT COMPONENT
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { jwtVerify } from 'jose';

const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET || 'super_secret_jwt_key_that_is_at_least_32_bytes_long'); // Must match secret in actions.ts

interface UserPayload {
  userId: string;
  email: string;
  mfaVerified: boolean;
  mfaEnabled: boolean;
}

export async function middleware(request: NextRequest) {
  const authToken = request.cookies.get('auth_token')?.value;
  const { pathname } = request.nextUrl;

  const publicPaths = ['/login', '/register', '/api/auth/login']; // Define public routes
  const mfaVerificationPath = '/mfa-verify';

  // Allow access to public paths without authentication
  if (publicPaths.includes(pathname) || pathname.startsWith('/_next')) {
    return NextResponse.next();
  }

  // If no auth token, redirect to login
  if (!authToken) {
    const url = request.nextUrl.clone();
    url.pathname = '/login';
    return NextResponse.redirect(url);
  }

  try {
    // Verify the JWT token
    const { payload } = await jwtVerify<UserPayload>(authToken, JWT_SECRET);

    // Check if MFA is enabled but not verified
    if (payload.mfaEnabled && !payload.mfaVerified) {
      // If user needs to verify MFA but tries to access other protected routes, redirect to MFA page
      if (pathname !== mfaVerificationPath) {
        const url = request.nextUrl.clone();
        url.pathname = mfaVerificationPath;
        return NextResponse.redirect(url);
      }
    }

    // If MFA is verified (or not enabled) and user tries to access MFA page, redirect to dashboard
    if ((!payload.mfaEnabled || payload.mfaVerified) && pathname === mfaVerificationPath) {
      const url = request.nextUrl.clone();
      url.pathname = '/dashboard';
      return NextResponse.redirect(url);
    }

    // Continue to the requested page if authenticated and MFA status is handled
    return NextResponse.next();

  } catch (error) {
    console.error('JWT verification failed:', error);
    // If token is invalid or expired, clear it and redirect to login
    const response = NextResponse.redirect(new URL('/login', request.url));
    response.cookies.delete('auth_token');
    return response;
  }
}

// Configuration for middleware to run on specific paths
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], // Exclude static files and API routes (if using separate API routes)
};

3. MFA Enrollment (TOTP Example) via Server Actions

Users should be able to enroll in MFA from their account settings. This involves generating a secret, displaying a QR code, and verifying the first code provided by the user.

3.1. MFA Setup Page (Client Component)

Displays a QR code and an input field for the verification code.

REACT COMPONENT
// app/dashboard/settings/mfa/page.tsx
'use client';

import { useState, useEffect } from 'react';
import Image from 'next/image';
import { generateMfaSecret, verifyMfaSetup } from '@/app/lib/actions'; // Server Actions

interface MfaSetupResult {
  success: boolean;
  secret?: string;
  qrCodeUrl?: string;
  message?: string;
}

export default function MfaSetupPage() {
  const [secret, setSecret] = useState<string | null>(null);
  const [qrCodeUrl, setQrCodeUrl] = useState<string | null>(null);
  const [verificationCode, setVerificationCode] = useState('');
  const [message, setMessage] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const setupMfa = async () => {
      setLoading(true);
      setMessage(null);
      const result: MfaSetupResult = await generateMfaSecret();
      if (result.success && result.secret && result.qrCodeUrl) {
        setSecret(result.secret);
        setQrCodeUrl(result.qrCodeUrl);
      } else {
        setMessage(result.message || 'Failed to generate MFA secret.');
      }
      setLoading(false);
    };
    setupMfa();
  }, []);

  const handleVerify = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setLoading(true);
    setMessage(null);

    const result = await verifyMfaSetup(verificationCode);
    if (result.success) {
      setMessage('MFA successfully enabled!');
      // Optionally redirect or update UI to reflect MFA status
      window.location.href = '/dashboard/settings';
    } else {
      setMessage(result.message || 'MFA verification failed. Please try again.');
    }
    setLoading(false);
  };

  if (loading && !secret) {
    return <div className="p-8 text-center">Loading MFA setup...</div>;
  }

  return (
    <div className="flex min-h-screen items-center justify-center bg-gray-100">
      <div className="w-full max-w-lg rounded-lg bg-white p-8 shadow-md">
        <h1 className="mb-6 text-center text-3xl font-bold text-gray-900">Enable Multi-Factor Authentication</h1>
        {message && (
          <div className={`mb-4 rounded-md p-3 ${message.includes('success') ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
            {message}
          </div>
        )}
        {qrCodeUrl && secret ? (
          <>
            <p className="mb-4 text-center text-gray-700">Scan this QR code with your authenticator app (e.g., Google Authenticator, Authy).</p>
            <div className="mb-6 flex justify-center">
              <Image src={qrCodeUrl} alt="MFA QR Code" width={200} height={200} />
            </div>
            <p className="mb-6 text-center font-mono text-sm text-gray-600">Manual Key: {secret}</p>
            <form onSubmit={handleVerify} className="space-y-4">
              <div>
                <label htmlFor="code" className="block text-sm font-medium text-gray-700">Verification Code</label>
                <input
                  id="code"
                  name="code"
                  type="text"
                  maxLength={6}
                  required
                  value={verificationCode}
                  onChange={(e) => setVerificationCode(e.target.value)}
                  className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-center text-lg tracking-widest shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
                  placeholder="123456"
                />
              </div>
              <div>
                <button
                  type="submit"
                  disabled={loading || verificationCode.length !== 6}
                  className="flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-50"
                >
                  {loading ? 'Verifying...' : 'Verify & Enable MFA'}
                </button>
              </div>
            </form>
          </>
        ) : (
          <p className="text-center text-gray-500">{message || 'Preparing MFA setup...'}</p>
        )}
      </div>
    </div>
  );
}

3.2. MFA Enrollment Server Actions

These actions handle secret generation, QR code generation, and the final verification to enable MFA for the user. Note that we update the user's record in the database and then re-issue the JWT with mfa_enabled: true and mfa_verified: true.

REACT COMPONENT
// app/lib/actions.ts (continued)
'use server';

import { cookies } from 'next/headers';
import { SignJWT, jwtVerify } from 'jose';
import { redirect } from 'next/navigation';
import { authenticator } from 'otplib'; // For TOTP generation/verification
import qrcode from 'qrcode'; // For QR code generation

// Ensure JWT_SECRET is available and matches
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET || 'super_secret_jwt_key_that_is_at_least_32_bytes_long');

// Helper to get current user payload from cookie
async function getCurrentUserPayload(): Promise<UserPayload | null> {
  const authToken = cookies().get('auth_token')?.value;
  if (!authToken) return null;
  try {
    const { payload } = await jwtVerify<UserPayload>(authToken, JWT_SECRET);
    return payload;
  } catch (error) {
    console.error('Failed to verify token in helper:', error);
    return null;
  }
}

// Mock User DB (expanded for MFA)
interface MockUser {
  id: string;
  email: string;
  password: string;
  mfaEnabled: boolean;
  mfaSecret: string | null;
}

const MOCK_DB_USERS_MFA: MockUser[] = [
  { id: 'user1', email: 'test@example.com', password: 'password123', mfaEnabled: false, mfaSecret: null },
  { id: 'user2', email: 'nomfa@example.com', password: 'password123', mfaEnabled: false, mfaSecret: null },
];

async function findUserById(id: string): Promise<MockUser | undefined> {
  return MOCK_DB_USERS_MFA.find(u => u.id === id);
}

async function updateUserMfa(userId: string, mfaEnabled: boolean, mfaSecret: string | null): Promise<boolean> {
  const userIndex = MOCK_DB_USERS_MFA.findIndex(u => u.id === userId);
  if (userIndex === -1) return false;
  MOCK_DB_USERS_MFA[userIndex].mfaEnabled = mfaEnabled;
  MOCK_DB_USERS_MFA[userIndex].mfaSecret = mfaSecret;
  return true;
}

// Generates a new MFA secret and QR code for the current user
export async function generateMfaSecret(): Promise<{ success: boolean; secret?: string; qrCodeUrl?: string; message?: string }> {
  const userPayload = await getCurrentUserPayload();
  if (!userPayload) {
    return { success: false, message: 'Not authenticated.' };
  }

  // Ensure this is a server action that only an authenticated user can call.
  // The middleware already handles basic auth, but further checks can be added.

  try {
    const secret = authenticator.generateSecret(); // Generate a new TOTP secret

    // Generate provisioning URL for QR code
    const otpauthUrl = authenticator.keyuri(userPayload.email, 'Next.js MFA App', secret);
    const qrCodeUrl = await qrcode.toDataURL(otpauthUrl);

    // Store the temporary secret in user's session or a temporary DB field
    // For simplicity, we'll store it directly, but in a real app,
    // this might be a temporary secret associated with a pending MFA setup.
    // For this example, we'll temporarily update the user (mocked).
    const user = await findUserById(userPayload.userId);
    if (user) {
        user.mfaSecret = secret; // Temporarily store, will be confirmed by verifyMfaSetup
    }


    return { success: true, secret, qrCodeUrl };
  } catch (error) {
    console.error('Failed to generate MFA secret:', error);
    return { success: false, message: 'Failed to generate MFA secret.' };
  }
}

// Verifies the user's first TOTP code and enables MFA
export async function verifyMfaSetup(code: string): Promise<{ success: boolean; message?: string }> {
  const userPayload = await getCurrentUserPayload();
  if (!userPayload) {
    return { success: false, message: 'Not authenticated.' };
  }

  const user = await findUserById(userPayload.userId);
  if (!user || !user.mfaSecret) {
    return { success: false, message: 'MFA setup not initiated or secret missing.' };
  }

  try {
    const isValid = authenticator.check(code, user.mfaSecret);

    if (isValid) {
      // Update user in DB: enable MFA and store the secret permanently
      const updated = await updateUserMfa(user.id, true, user.mfaSecret);
      if (!updated) {
          return { success: false, message: 'Failed to update user MFA status.' };
      }

      // Re-issue JWT with updated mfaVerified and mfaEnabled flags
      const newPayload: UserPayload = { ...userPayload, mfaEnabled: true, mfaVerified: true };
      const newToken = await new SignJWT(newPayload)
        .setProtectedHeader({ alg: 'HS256' })
        .setIssuedAt()
        .setExpirationTime('1h')
        .sign(JWT_SECRET);

      cookies().set('auth_token', newToken, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        maxAge: 60 * 60 * 24 * 7,
        path: '/',
        sameSite: 'lax',
      });

      return { success: true, message: 'MFA enabled successfully!' };
    } else {
      return { success: false, message: 'Invalid verification code.' };
    }
  } catch (error) {
    console.error('Failed to verify MFA setup:', error);
    return { success: false, message: 'An unexpected error occurred during verification.' };
  }
}

4. MFA Verification During Login (TOTP) with Server Actions

Once MFA is enabled, users must provide a TOTP code during login (or when the middleware detects an mfa_pending state). This flow verifies the code and updates the JWT to reflect MFA verification.

4.1. MFA Verification Page (Client Component)

A dedicated page for users to enter their TOTP code.

REACT COMPONENT
// app/mfa-verify/page.tsx
'use client';

import { useState } from 'react';
import { verifyMfaLogin } from '@/app/lib/actions'; // Server Action

interface MfaVerifyResult {
  success: boolean;
  message?: string;
  redirectTo?: string;
}

export default function MfaVerifyPage() {
  const [code, setCode] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setLoading(true);
    setError(null);

    const result: MfaVerifyResult = await verifyMfaLogin(code);

    if (result.success) {
      if (result.redirectTo) {
        window.location.href = result.redirectTo; // Client-side redirect
      }
    } else {
      setError(result.message || 'MFA verification failed. Please try again.');
    }
    setLoading(false);
  };

  return (
    <div className="flex min-h-screen items-center justify-center bg-gray-100">
      <div className="w-full max-w-md rounded-lg bg-white p-8 shadow-md">
        <h1 className="mb-6 text-center text-3xl font-bold text-gray-900">MFA Verification Required</h1>
        <p className="mb-6 text-center text-gray-700">Please enter the 6-digit code from your authenticator app.</p>
        <form onSubmit={handleSubmit} className="space-y-6">
          <div>
            <label htmlFor="mfaCode" className="block text-sm font-medium text-gray-700">Verification Code</label>
            <input
              id="mfaCode"
              name="mfaCode"
              type="text"
              maxLength={6}
              required
              value={code}
              onChange={(e) => setCode(e.target.value)}
              className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-center text-lg tracking-widest shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
              placeholder="123456"
            />
          </div>
          {error && <p className="text-sm text-red-600">{error}</p>}
          <div>
            <button
              type="submit"
              disabled={loading || code.length !== 6}
              className="flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-50"
            >
              {loading ? 'Verifying...' : 'Verify MFA'}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

4.2. MFA Login Verification Server Action

This server action verifies the TOTP code and, if valid, re-issues the JWT with mfa_verified: true, allowing access to protected resources.

REACT COMPONENT
// app/lib/actions.ts (continued)
'use server';

// ... (imports from previous actions.ts sections)
// import { cookies } from 'next/headers';
// import { SignJWT, jwtVerify } from 'jose';
// import { redirect } from 'next/navigation';
// import { authenticator } from 'otplib'; // For TOTP generation/verification
// import qrcode from 'qrcode'; // For QR code generation

// (JWT_SECRET, UserPayload, getCurrentUserPayload, MOCK_DB_USERS_MFA, findUserById, updateUserMfa already defined)


// Verifies MFA code during login flow
export async function verifyMfaLogin(code: string): Promise<{ success: boolean; message?: string; redirectTo?: string }> {
  const userPayload = await getCurrentUserPayload();
  if (!userPayload) {
    return { success: false, message: 'Not authenticated or session expired.', redirectTo: '/login' };
  }

  // Ensure MFA is enabled for this user and not already verified
  if (!userPayload.mfaEnabled || userPayload.mfaVerified) {
    // If MFA is not enabled or already verified, redirect to dashboard
    return { success: true, redirectTo: '/dashboard' };
  }

  const user = await findUserById(userPayload.userId);
  if (!user || !user.mfaSecret) {
    // This state indicates a data inconsistency or an attempt to bypass MFA.
    // In production, log this and potentially force logout.
    return { success: false, message: 'MFA not configured for your account.', redirectTo: '/login' };
  }

  try {
    const isValid = authenticator.check(code, user.mfaSecret);

    if (isValid) {
      // Re-issue JWT with mfaVerified: true
      const newPayload: UserPayload = { ...userPayload, mfaVerified: true };
      const newToken = await new SignJWT(newPayload)
        .setProtectedHeader({ alg: 'HS256' })
        .setIssuedAt()
        .setExpirationTime('1h') // Token expires in 1 hour
        .sign(JWT_SECRET);

      cookies().set('auth_token', newToken, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        maxAge: 60 * 60 * 24 * 7,
        path: '/',
        sameSite: 'lax',
      });

      return { success: true, message: 'MFA verified successfully!', redirectTo: '/dashboard' };
    } else {
      return { success: false, message: 'Invalid verification code.' };
    }
  } catch (error) {
    console.error('Failed to verify MFA login:', error);
    return { success: false, message: 'An unexpected error occurred during MFA verification.' };
  }
}

5. Protecting Sensitive Server Actions

While Middleware protects page access, it's crucial to also verify MFA status directly within Server Actions that perform sensitive operations (e.g., changing passwords, making payments). This provides a defense-in-depth approach, preventing authenticated but MFA-unverified users from performing critical actions.

REACT COMPONENT
// app/lib/actions.ts (continued)
'use server';

// ... (imports and existing helpers like getCurrentUserPayload)

// Example: Sensitive action requiring MFA verification
export async function updateSensitiveSetting(newSettingValue: string): Promise<{ success: boolean; message?: string }> {
  const userPayload = await getCurrentUserPayload();

  if (!userPayload) {
    return { success: false, message: 'Authentication required.' };
  }

  // CRITICAL: Check if MFA is enabled AND verified
  if (userPayload.mfaEnabled && !userPayload.mfaVerified) {
    return { success: false, message: 'MFA verification required for this action.' };
  }

  // Proceed with sensitive operation
  try {
    // In a real application, interact with your database here
    console.log(`User ${userPayload.userId} updated sensitive setting to: ${newSettingValue}`);
    // await db.users.update(userPayload.userId, { sensitiveSetting: newSettingValue });

    return { success: true, message: 'Sensitive setting updated successfully.' };
  } catch (error) {
    console.error('Failed to update sensitive setting:', error);
    return { success: false, message: 'Failed to update sensitive setting due to a server error.' };
  }
}

// Example Client Component for a sensitive action
// app/dashboard/settings/sensitive-area.tsx
'use client';

import { useState } from 'react';
import { updateSensitiveSetting } from '@/app/lib/actions';

export function SensitiveSettingForm() {
  const [setting, setSetting] = useState('');
  const [message, setMessage] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setLoading(true);
    setMessage(null);

    const result = await updateSensitiveSetting(setting);
    if (result.success) {
      setMessage('Setting updated!');
      setSetting('');
    } else {
      setMessage(result.message || 'Failed to update setting.');
    }
    setLoading(false);
  };

  return (
    <div className="mt-8 rounded-lg bg-red-50 p-6 shadow-sm">
      <h3 className="mb-4 text-xl font-semibold text-red-800">Sensitive Area</h3>
      <p className="mb-4 text-sm text-red-700">This action requires MFA verification if enabled on your account.</p>
      <form onSubmit={handleSubmit} className="space-y-4">
        <div>
          <label htmlFor="sensitiveInput" className="block text-sm font-medium text-red-700">New Sensitive Value</label>
          <input
            id="sensitiveInput"
            name="sensitiveInput"
            type="text"
            required
            value={setting}
            onChange={(e) => setSetting(e.target.value)}
            className="mt-1 block w-full rounded-md border border-red-300 px-3 py-2 shadow-sm focus:border-red-500 focus:ring-red-500 sm:text-sm"
          />
        </div>
        {message && (
          <p className={`text-sm ${message.includes('success') ? 'text-green-600' : 'text-red-600'}`}>
            {message}
          </p>
        )}
        <div>
          <button
            type="submit"
            disabled={loading}
            className="flex justify-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 disabled:opacity-50"
          >
            {loading ? 'Updating...' : 'Update Sensitive Setting'}
          </button>
        </div>
      </form>
    </div>
  );
}

By combining Next.js Middleware and Server Actions, you can build a robust and secure multi-factor authentication system. Middleware provides initial gatekeeping, redirecting users based on their authentication and MFA status, while Server Actions handle the critical logic of authenticating, enrolling, and verifying MFA factors securely on the server. Always remember to use strong, unique secrets stored in environment variables and apply a defense-in-depth strategy, validating authorization and MFA status at every critical server boundary.

📚 More Resources

Check out related content:

Looking for beautiful UI layouts and CSS animations?

🎨 Need Design? Get Pure CSS Inspiration →
ℹ️ Note: Code is generated for educational purposes.

Comments

Popular posts from this blog

How to Architect Accessible Tailwind Components in Next.js 15 with TypeScript (2026)

Optimizing Zustand State Architecture for Next.js 15 App Router & Server Components with TypeScript (2026)

Effective TypeScript Patterns for Scalable Next.js 15 Logic Architectures (2026)