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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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 →
Comments
Post a Comment