• Fri, Mar 2026

Vue.js Add 2FA with Full Backend Example

Vue.js Add 2FA with Full Backend Example

Previously, we walked through setting up a Vue.js frontend for enabling Two-Factor Authentication (2FA). But without a backend, that’s like building a vault with no locks. In this extended tutorial, I’ll show you a complete **Node.js + Express backend** working hand-in-hand with a **Vue.js frontend** to create a real-world 2FA system.

Step 1: Backend Setup

First, let’s spin up a new Node.js + Express backend. Run the following commands:


mkdir vue-2fa-backend
cd vue-2fa-backend
npm init -y
npm install express cors body-parser speakeasy qrcode

Create server.js


// server.js
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const speakeasy = require("speakeasy");
const qrcode = require("qrcode");

const app = express();
app.use(cors());
app.use(bodyParser.json());

// Fake DB (for demo)
let users = [
  { id: 1, username: "john", password: "123456", secret: null, is2FAEnabled: false }
];

// Login API
app.post("/login", (req, res) => {
  const { username, password } = req.body;
  const user = users.find(u => u.username === username && u.password === password);

  if (!user) {
    return res.status(401).json({ message: "Invalid credentials" });
  }

  if (user.is2FAEnabled) {
    return res.json({ requires2FA: true, userId: user.id });
  }

  res.json({ message: "Logged in without 2FA", token: "fake-jwt-token" });
});

// 2FA Setup API
app.post("/2fa/setup", (req, res) => {
  const { userId } = req.body;
  const user = users.find(u => u.id === userId);

  if (!user) return res.status(404).json({ message: "User not found" });

  const secret = speakeasy.generateSecret({ length: 20 });
  user.secret = secret.base32;

  qrcode.toDataURL(secret.otpauth_url, (err, data) => {
    res.json({ secret: user.secret, qr: data });
  });
});

// 2FA Verify API
app.post("/2fa/verify", (req, res) => {
  const { userId, token } = req.body;
  const user = users.find(u => u.id === userId);

  if (!user || !user.secret) {
    return res.status(404).json({ message: "User or secret not found" });
  }

  const verified = speakeasy.totp.verify({
    secret: user.secret,
    encoding: "base32",
    token
  });

  if (verified) {
    user.is2FAEnabled = true;
    return res.json({ verified: true, token: "secure-jwt-token" });
  }

  res.json({ verified: false });
});

// Start server
app.listen(3000, () => {
  console.log("2FA backend running on http://localhost:3000");
});

This backend handles login, 2FA setup (QR code + secret), and verification. Now, let’s wire it up to Vue.

Step 2: Vue.js Frontend Integration

In your Vue project, make two components: Setup2FA.vue and LoginWith2FA.vue.

Setup2FA.vue


<template>
  <div>
    <h2>Setup Two-Factor Authentication</h2>

    <div v-if="qrCode">
      <img :src="qrCode" alt="Scan QR code" />
      <p>Enter the 6-digit code:</p>
      <input v-model="token" placeholder="123456" />
      <button @click="verifyToken">Verify</button>
    </div>

    <div v-else>
      <button @click="generateSecret">Enable 2FA</button>
    </div>

    <p v-if="verificationStatus === true">✅ 2FA Enabled!</p>
    <p v-if="verificationStatus === false">❌ Invalid Code.</p>
  </div>
</template>

<script setup>
import { ref } from "vue";
import axios from "axios";

const qrCode = ref(null);
const token = ref("");
const verificationStatus = ref(null);
const userId = 1; // Fake logged-in user

const generateSecret = async () => {
  const res = await axios.post("http://localhost:3000/2fa/setup", { userId });
  qrCode.value = res.data.qr;
};

const verifyToken = async () => {
  const res = await axios.post("http://localhost:3000/2fa/verify", {
    userId,
    token: token.value
  });
  verificationStatus.value = res.data.verified;
};
</script>

LoginWith2FA.vue


<template>
  <div>
    <h2>Login</h2>

    <div v-if="!requires2FA">
      <input v-model="username" placeholder="Username" />
      <input v-model="password" type="password" placeholder="Password" />
      <button @click="login">Login</button>
    </div>

    <div v-else>
      <p>Enter 2FA Code</p>
      <input v-model="token" placeholder="123456" />
      <button @click="verify2FA">Verify</button>
    </div>

    <p v-if="loginStatus">{{ loginStatus }}</p>
  </div>
</template>

<script setup>
import { ref } from "vue";
import axios from "axios";

const username = ref("john");
const password = ref("123456");
const token = ref("");
const requires2FA = ref(false);
const loginStatus = ref("");
const userId = ref(null);

const login = async () => {
  const res = await axios.post("http://localhost:3000/login", {
    username: username.value,
    password: password.value
  });

  if (res.data.requires2FA) {
    requires2FA.value = true;
    userId.value = res.data.userId;
  } else {
    loginStatus.value = res.data.message;
  }
};

const verify2FA = async () => {
  const res = await axios.post("http://localhost:3000/2fa/verify", {
    userId: userId.value,
    token: token.value
  });

  if (res.data.verified) {
    loginStatus.value = "✅ Logged in with 2FA!";
  } else {
    loginStatus.value = "❌ Invalid 2FA code.";
  }
};
</script>

Step 3: Run It All Together

  1. Start backend: node server.js
  2. Start Vue frontend: npm run dev
  3. Visit the app in browser:
    • First, enable 2FA using Setup2FA.vue.
    • Then, try logging in using LoginWith2FA.vue. You’ll be prompted for the 2FA code.

Step 4: Real-World Enhancements

  • JWT Integration: Instead of “fake-jwt-token”, return real JWTs from backend.
  • Database Storage: Save secrets securely in a database, not in memory.
  • Optional 2FA: Allow users to enable/disable 2FA from settings.
  • Recovery Codes: Provide backup codes for emergency login.

Final Thoughts

And there you go — a full-stack 2FA implementation with Vue.js frontend and Node.js backend. You’ve now got everything from QR code setup to token verification fully working. In practice, this is exactly how apps like Google, GitHub, and banking platforms protect users from account breaches.

Once you’ve played around with this, try adding JWT authentication and persisting sessions to make it production-ready. Trust me, your future self will thank you when hackers bounce off your app’s login page like rubber balls on concrete.

This website uses cookies to enhance your browsing experience. By continuing to use this site, you consent to the use of cookies. Please review our Privacy Policy for more information on how we handle your data. Cookie Policy