Multi-Factor Authentication (MFA)
A robust multi-factor authentication system for Express, featuring OTP verification via email and secure JWT token handling.
Overview
Adds multi-factor authentication (MFA) to your Express app using email-based one-time passwords (OTPs). After successful email verification, JWT tokens are issued for session security. Includes OTP expiry handling, Nodemailer email delivery, and in-memory user storage for quick testing (replace with a real DB for production).
Installation
What This Does
Installs ready-to-use routes, controllers, and services for multi-factor authentication using OTP verification and JWT.
Files & Folders Created
| File / Path | Description |
|---|---|
| /routes/auth.js | Defines routes for register, login, verify, and profile endpoints. |
| /controllers/authController.js | Manages authentication and OTP verification flow. |
| /services/authService.js | Handles OTP generation, storage, JWT issuance, and user management. |
| /services/emailService.js | Sends OTPs via Nodemailer using your SMTP credentials. |
| /middleware/auth.js | Protects routes by verifying JWT tokens. |
| /.env.example | Example environment configuration for SMTP and JWT setup. |
Files to be modified
| File / Path | Description |
|---|---|
| server.js | Mounts /api/auth routes for MFA authentication endpoints. |
Configuration
# JWT Configuration JWT_SECRET=your-super-secret-key JWT_EXPIRES_IN=7d # OTP OTP_EXPIRY_MINUTES=10 # SMTP Configuration SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=your-email@gmail.com SMTP_PASS=your-app-password SMTP_FROM_NAME=Your App Name PORT=3000 NODE_ENV=development
Frontend Integration
Here’s a complete Next.js example showing how to register, verify OTP, and handle JWT tokens using this MFA component.
Registers a user and sends an OTP to the provided email for verification.
Initiates login by verifying credentials and sending an OTP email.
Verifies the OTP and returns a JWT token upon success.
Fetches the authenticated user’s details using JWT.
Example
1// app/components/MFAFlow.jsx
2'use client';
3import { useState } from 'react';
4
5export default function MFAFlow() {
6 const [step, setStep] = useState('register');
7 const [email, setEmail] = useState('');
8 const [password, setPassword] = useState('');
9 const [otp, setOtp] = useState('');
10 const [tempToken, setTempToken] = useState('');
11 const [accessToken, setAccessToken] = useState('');
12 const [message, setMessage] = useState('');
13
14 async function handleRegister(e) {
15 e.preventDefault();
16 setMessage('');
17 const res = await fetch('/api/auth/register', {
18 method: 'POST',
19 headers: { 'Content-Type': 'application/json' },
20 body: JSON.stringify({ email, password }),
21 });
22 const data = await res.json();
23 if (res.ok) {
24 setTempToken(data.token);
25 setStep('verify');
26 setMessage('OTP sent to your email.');
27 } else {
28 setMessage(data.error || 'Registration failed.');
29 }
30 }
31
32 async function handleVerify(e) {
33 e.preventDefault();
34 const res = await fetch('/api/auth/verify', {
35 method: 'POST',
36 headers: { 'Content-Type': 'application/json' },
37 body: JSON.stringify({ otp, tempToken }),
38 });
39 const data = await res.json();
40 if (res.ok) {
41 localStorage.setItem('accessToken', data.token);
42 setAccessToken(data.token);
43 setStep('authenticated');
44 setMessage('Verification successful! Logged in.');
45 } else {
46 setMessage(data.error || 'OTP verification failed.');
47 }
48 }
49
50 return (
51 <div className="max-w-sm mx-auto bg-black/20 border border-white/10 rounded-xl p-6 text-white space-y-4">
52 <h2 className="text-xl font-semibold">Multi-Factor Authentication</h2>
53
54 {step === 'register' && (
55 <form onSubmit={handleRegister} className="space-y-3">
56 <input
57 type="email"
58 placeholder="Email"
59 value={email}
60 onChange={(e) => setEmail(e.target.value)}
61 className="w-full px-3 py-2 rounded-md bg-white/10 border border-white/20 focus:outline-none"
62 required
63 />
64 <input
65 type="password"
66 placeholder="Password"
67 value={password}
68 onChange={(e) => setPassword(e.target.value)}
69 className="w-full px-3 py-2 rounded-md bg-white/10 border border-white/20 focus:outline-none"
70 required
71 />
72 <button
73 type="submit"
74 className="w-full bg-emerald-500 hover:bg-emerald-600 text-white font-semibold py-2 rounded-md transition-colors"
75 >
76 Register
77 </button>
78 </form>
79 )}
80
81 {step === 'verify' && (
82 <form onSubmit={handleVerify} className="space-y-3">
83 <input
84 type="text"
85 placeholder="Enter OTP"
86 value={otp}
87 onChange={(e) => setOtp(e.target.value)}
88 className="w-full px-3 py-2 rounded-md bg-white/10 border border-white/20 focus:outline-none"
89 required
90 />
91 <button
92 type="submit"
93 className="w-full bg-emerald-500 hover:bg-emerald-600 text-white font-semibold py-2 rounded-md transition-colors"
94 >
95 Verify OTP
96 </button>
97 </form>
98 )}
99
100 {step === 'authenticated' && (
101 <div className="p-3 bg-emerald-500/10 rounded-lg border border-emerald-500/20">
102 <p className="text-sm text-emerald-300">✅ Logged in with JWT</p>
103 <p className="text-xs break-all text-white/70 mt-2">
104 {accessToken}
105 </p>
106 </div>
107 )}
108
109 {message && <p className="text-amber-400 text-sm">{message}</p>}
110 </div>
111 );
112}Usage
1// Protect routes with JWT
2const express = require('express');
3const authMiddleware = require('./src/middleware/auth');
4const app = express();
5
6app.get('/api/me', authMiddleware, (req, res) => {
7 res.json({ message: 'Protected route', user: req.user });
8});