Two-factor authentication (2FA) adds an extra layer of security to your apps. In this guide, we’ll use express and auth-verify, a Node.js library, to implement TOTP (Time-based One-Time Password) compatible with Google Authenticator.
We’ll cover:
- Generating a TOTP secret
- Creating a QR code for users
- Verifying user-entered codes
Install
We should install all necessary packages from NPM.
npm install express path auth-verify
File structure
Our file structure should be like this:
├── server.js
└── public/
├── index.html
Import
We should setup all necessary packages to server.js.
const express = require("express");
const path = require("path");
const AuthVerify = require("auth-verify");
const app = express();
const PORT = 3000;
app.use(express.static(path.join(__dirname, 'public')));
const auth = new AuthVerify();
const secret = auth.totp.secret(); // generating a secret for totp
// Start server
app.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`));
Make web page
We'll make index.html for showing QR code to user for getting TOTP codes.
<!DOCTYPE html>
<html>
<head>
<title>Getting QR code for 2FA</title>
</head>
<body>
<h1>Getting QR code for 2FA</h1>
<button id="getQRBtn">Get QR</button><br>
<img id="qrImage" /><br>
<input type="text" id="userCode" placeholder="Enter your code">
<button id="verifyBtn">Verify code</button><br>
<span></span>
</body>
</html>
Backend part
Firstly we'll send our index.html file with GET route:
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', (req, res)=>{
res.sendFile(path.join(__dirname, 'index.html'));
});
Secondly, we should generate TOTP code and QR data in server and we should send it to client.
app.get('/api/qr', async (req, res)=>{
try {
// making otpauth URI for QR code
const uri = auth.totp.uri({
label: "user@example.com",
issuer: "2FA system for dev.to",
secret
});
// generating QR code
const qr = await auth.totp.qr(uri);
// We'll send this QR code to the client
res.json({ qr });
} catch(err){
console.error(err);
res.status(500).json({ error: 'Failed to generate QR' });
}
});
In step 3 we'll check user code whether is it correct or not:
app.post('/api/verify', async (req, res) => {
const { code } = req.body; // we'll recieve code from client
if (!code) { // for debugging...
return res.status(400).json({ error: "No code provided" });
}
try {
const verified = await auth.totp.verify(secret, code); // verifying user code
res.json({ verified }); // and we'll send the result to the client
} catch(err) {
console.error(err);
res.status(400).json({ error: "Verification failed", details: err.message });
}
});
Frontend part
Now we show user QR code to the web page and we should get code that was inserted by user
<script src="https://cdn.jsdelivr.net/gh/jahongir2007/auth-verify/authverify.client.js"></script>
<script>
const qrImage = document.getElementById('qrImage');
const qrBtn = document.getElementById('getQRBtn');
const verifyBtn = document.getElementById('verifyBtn');
const userCodeInput = document.getElementById('userCode');
const resultSpan = document.querySelector('span');
const auth = new AuthVerify({
apiBase: 'http://localhost:3000',
qrEl: qrImage
});
// GET QR code
qrBtn.addEventListener('click', async () => {
auth.get('/api/qr'); // set the URL for GET
await auth.qr(); // fetch and display QR
});
// VERIFY code
verifyBtn.addEventListener('click', async () => {
const userCode = userCodeInput.value;
auth.post('/api/verify'); // set URL for POST
const result = await auth.verify(userCode); // send code to server
if (result.verified) {
resultSpan.innerText = '✅ Verified!';
} else {
resultSpan.innerText = '❌ Invalid code';
}
});
</script>
Result
Full codes
index.html:
<!DOCTYPE html>
<html>
<head>
<title>Getting QR code for 2FA</title>
</head>
<body>
<h1>Getting QR code for 2FA</h1>
<button id="getQRBtn">Get QR</button><br>
<img id="qrImage" /><br>
<input type="text" id="userCode" placeholder="Enter your code">
<button id="verifyBtn">Verify code</button><br>
<span></span>
<script src="https://cdn.jsdelivr.net/gh/jahongir2007/auth-verify/authverify.client.js"></script>
<script>
const qrImage = document.getElementById('qrImage');
const qrBtn = document.getElementById('getQRBtn');
const verifyBtn = document.getElementById('verifyBtn');
const userCodeInput = document.getElementById('userCode');
const resultSpan = document.querySelector('span');
const auth = new AuthVerify({
apiBase: 'http://localhost:3000',
qrEl: qrImage
});
// GET QR code
qrBtn.addEventListener('click', async () => {
auth.get('/api/qr'); // set the URL for GET
await auth.qr(); // fetch and display QR
});
// VERIFY code
verifyBtn.addEventListener('click', async () => {
const userCode = userCodeInput.value;
auth.post('/api/verify'); // set URL for POST
const result = await auth.verify(userCode); // send code to server
if (result.verified) {
resultSpan.innerText = '✅ Verified!';
} else {
resultSpan.innerText = '❌ Invalid code';
}
});
</script>
</body>
</html>
server.js:
const express = require('express');
const path = require('path');
const AuthVerify = require('auth-verify');
const app = express();
const PORT = 3000;
// Serve everything inside /public
app.use(express.json());
const auth = new AuthVerify();
const secret = auth.totp.secret(); // generating a secret
app.get('/api/qr', async (req, res)=>{
try {
// making otpauth URI for QR code
const uri = auth.totp.uri({
label: "user@example.com",
issuer: "2FA system for dev.to",
secret
});
// generating QR code
const qr = await auth.totp.qr(uri);
// We'll send this QR code to the client
res.json({ qr });
} catch(err){
console.error(err);
res.status(500).json({ error: 'Failed to generate QR' });
}
});
app.post('/api/verify', async (req, res) => {
const { code } = req.body;
if (!code) {
return res.status(400).json({ error: "No code provided" });
}
try {
const verified = await auth.totp.verify(secret, code);
res.json({ verified });
} catch(err) {
console.error(err);
res.status(400).json({ error: "Verification failed", details: err.message });
}
});
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', (req, res)=>{
res.sendFile(path.join(__dirname, 'index.html'));
});
app.listen(PORT, ()=> console.log(`Server is runing at http://localhost:${PORT}`));
I hope that would be useful for you

Top comments (0)