Two-Factor Authentication (2FA) adds an extra layer of security to the login process by requiring not only the user's credentials but also a second factor, such as a temporary code generated by an authentication app.
Prerequisites
- Node.js installed
- An app such as Google Authenticator or Authy
- An existing Node.js project
1. Installing the Required Packages
npm install speakeasy qrcode express body-parser
These packages are used for:
- speakeasy: generating and verifying TOTP tokens
- qrcode: generating the QR code to scan
- express: creating the server
- body-parser: parsing the request body
2. Basic Server Setup
const express = require('express');
const bodyParser = require('body-parser');
const speakeasy = require('speakeasy');
const qrcode = require('qrcode');
const app = express();
app.use(bodyParser.json());
let temp_secret, user_secret;
3. Generating the Secret and QR Code
When the user enables 2FA, we generate a secret and a QR code to be scanned with the authentication app.
app.get('/generate', (req, res) => {
const secret = speakeasy.generateSecret({ name: 'MyAppName' });
temp_secret = secret;
qrcode.toDataURL(secret.otpauth_url, (err, data_url) => {
res.json({ qr: data_url, secret: secret.base32 });
});
});
4. Verifying the Token
When the user enters the code generated by their app, we check if it is correct.
app.post('/verify', (req, res) => {
const { token } = req.body;
const verified = speakeasy.totp.verify({
secret: temp_secret.base32,
encoding: 'base32',
token,
window: 1
});
if (verified) {
user_secret = temp_secret;
res.send('2FA successfully enabled!');
} else {
res.status(400).send('Invalid token');
}
});
5. Continuous Token Verification at Login
When a user logs in, we compare the entered token with the stored secret.
app.post('/login-2fa', (req, res) => {
const { token } = req.body;
const verified = speakeasy.totp.verify({
secret: user_secret.base32,
encoding: 'base32',
token,
window: 1
});
if (verified) {
res.send('Login successful with 2FA!');
} else {
res.status(400).send('Incorrect or expired token');
}
});
6. Starting the Server
app.listen(3000, () => {
console.log('Server running at http://localhost:3000');
});
Conclusion
With just a few simple steps, we have integrated 2FA into our Node.js project. It is important to securely store user secrets and use HTTPS for all communications. For production projects, it is recommended to store secrets in a secure database and carefully manage token timeouts and validity windows.