Integrating Two-Factor Authentication (2FA) into your authentication system involves additional steps beyond a basic authentication setup. Below is a step-by-step guide to enhance your Node.js and Express application with 2FA using the speakeasy
library for generating and validating time-based one-time passwords (TOTPs).
Step 1: Set Up Your Project
Create a new project folder:
mkdir 2fa-authentication cd 2fa-authentication
Initialize a new Node.js project:
npm init -y
Install necessary dependencies:
npm install express ejs body-parser speakeasy qrcode
Step 2: Create Your Folder Structure
Create the following folder structure:
2fa-authentication/
|-- src/
| |-- views/
| |-- index.ejs
| |-- login.ejs
| |-- enable-2fa.ejs
| |-- routes/
| |-- auth.js
| |-- app.js
|-- server.js
|-- .gitignore
|-- package.json
Step 3: Set Up Express Server
In app.js
, set up an Express server:
const express = require('express');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
app.set('view engine', 'ejs');
app.use(express.static(path.join(__dirname, 'src/public')));
app.use(express.urlencoded({ extended: true }));
// Routes
const authRoutes = require('./src/routes/auth');
app.use('/auth', authRoutes);
app.get('/', (req, res) => {
res.render('index');
});
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
Step 4: Create Authentication Routes
In routes/auth.js
, set up routes for handling authentication and 2FA:
const express = require('express');
const speakeasy = require('speakeasy');
const qrcode = require('qrcode');
const router = express.Router();
const users = []; // Temporary storage for user data
router.get('/login', (req, res) => {
res.render('login');
});
router.post('/login', (req, res) => {
const { username, password, token } = req.body;
const user = users.find(u => u.username === username);
if (!user || user.password !== password) {
return res.status(401).send('Invalid username or password');
}
const isValidToken = speakeasy.totp.verify({
secret: user.secret,
encoding: 'base32',
token,
});
if (!isValidToken) {
return res.status(401).send('Invalid 2FA token');
}
res.send('Login successful!');
});
router.get('/enable-2fa', (req, res) => {
const secret = speakeasy.generateSecret({ length: 20 });
const otpauth_url = speakeasy.otpauthURL({
secret: secret.base32,
label: 'MyApp',
issuer_name: 'MyApp',
});
qrcode.toDataURL(otpauth_url, (err, data_url) => {
if (err) {
return res.status(500).send('Error generating QR code');
}
res.render('enable-2fa', { secret: secret.base32, qrCode: data_url });
});
});
router.post('/enable-2fa', (req, res) => {
const { username, password, secret, token } = req.body;
if (users.some(u => u.username === username)) {
return res.status(400).send('Username already exists');
}
users.push({
username,
password,
secret,
});
const isValidToken = speakeasy.totp.verify({
secret,
encoding: 'base32',
token,
});
if (!isValidToken) {
users.pop(); // Remove user if 2FA setup fails
return res.status(401).send('Invalid 2FA token');
}
res.send('2FA enabled successfully!');
});
module.exports = router;
Step 5: Create Views
In views/
, create the following EJS templates:
index.ejs
: Display the main page with navigation links.login.ejs
: Form for users to log in.enable-2fa.ejs
: Form for users to enable 2FA.
Step 6: Create Main Page
In views/index.ejs
, create the main page:
<!-- views/index.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2FA Authentication</title>
</head>
<body>
<h1>2FA Authentication</h1>
<ul>
<li><a href="/auth/login">Login</a></li>
<li><a href="/auth/enable-2fa">Enable 2FA</a></li>
</ul>
</body>
</html>
Step 7: Create Login Form
In views/login.ejs
, create the login form:
<!-- views/login.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form action="/auth/login" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<br>
<label for="token">2FA Token:</label>
<input type="text" id="token" name="token" required>
<br>
<button type="submit">Login</button>
</form>
</body>
</html>
Step 8: Create Enable 2FA Form
In views/enable-2fa.ejs
, create the enable 2FA form:
<!-- views/enable-2fa.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enable 2FA</title>
</head>
<body>
<h1>Enable 2FA</h1>
<p>Scan the QR code using a 2FA authenticator app (e.g., Google Authenticator).</p>
<img src="<%= qrCode %>" alt="QR Code">
<form action="/auth/enable-2fa" method="post">
<input type="hidden" name="username" value="<%= username %>">
<input type="hidden" name="password"
value="<%= password %>">
<input type="hidden" name="secret" value="<%= secret %>">
<label for="token">Enter 2FA Token:</label>
<input type="text" id="token" name="token" required>
<br>
<button type="submit">Enable 2FA</button>
</form>
</body>
</html>
Step 9: Run Your Application
In server.js
, use the exported app
to start the application:
const app = require('./src/app');
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
Start the server:
node server.js
Visit http://localhost:3000 in your browser to see and interact with your 2FA authentication system.
Congratulations! You've successfully enhanced your authentication system with Two-Factor Authentication (2FA) using Node.js, Express, and the speakeasy
library. Customize and expand this application based on your specific requirements.