Two-Factor Authentication (2FA) with Node and Express

Two-Factor Authentication (2FA) with Node and Express

ยท

4 min read

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

  1. Create a new project folder:

     mkdir 2fa-authentication
     cd 2fa-authentication
    
  2. Initialize a new Node.js project:

     npm init -y
    
  3. 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.

Did you find this article valuable?

Support Revive Coding by becoming a sponsor. Any amount is appreciated!

ย