This article explains how to implement TOTP-based two-factor authentication in a Node.js admin panel. It uses speakeasy to generate shared secrets, bind authenticator apps, verify two-step logins, and control rollout with feature flags, helping reduce the risk of credential stuffing and weak-password abuse. Keywords: Node.js, TOTP, 2FA.
This TOTP two-factor authentication solution is designed for admin systems
| Parameter | Description |
|---|---|
| Language | Node.js / JavaScript |
| Protocol | otpauth / TOTP |
| Use Case | Security hardening for admin panel logins |
| Core Dependency | speakeasy |
| Authentication Medium | Authy, 2FAS, Feishu helper bot |
| Data Fields | secret, otpauthUrl |
| Original Platform | CNBlogs technical article |
| Star Count | Not provided in the original article; speakeasy is a commonly used community library |
Even if the admin system already supports encrypted password transmission, frequent-login blocking, and account lockout after repeated failures, it may still face risks from incomplete logging, credential stuffing, and unauthorized access after password leakage. The value of two-factor authentication lies in upgrading the requirement from “knows the password” to “knows the password and possesses the device.”
Common 2FA approaches include SMS verification codes, TOTP one-time codes, and hardware security keys. SMS is easy to deploy but offers weaker resistance to attacks. Hardware keys are the strongest option but come with higher cost. For most admin systems, TOTP provides the best balance of security, cost, and operational maintainability.
The core TOTP mechanism is a strong fit for fast server-side integration
TOTP generates a one-time verification code from a shared secret and the current time slice. Both the server and the authenticator app on the user’s phone hold the same secret and apply the same algorithm, so they can generate the same 6-digit or 8-digit dynamic code within the same time window.
const speakeasy = require('speakeasy');
// Generate a secret that can be shared by the server and client
const secret = speakeasy.generateSecret({ length: 20 });
console.log({
secret: secret.base32, // Core secret to store in the database
url: secret.otpauth_url // otpauth URL used to generate the QR code
});
This code generates the shared TOTP secret and the QR-based binding URL.
Secret generation and account binding are the first step of the implementation
Node.js can use speakeasy directly to generate the secret. The result usually includes two parts: a base32-formatted secret and an otpauth_url. The former is used for server-side verification, while the latter is used to display a QR code so the authenticator app can import the account by scanning it.
router.get('/get/f2a', async (ctx) => {
const secret = speakeasy.generateSecret({ length: 20 });
ctx.body = {
code: 0,
data: {
secret: secret.base32, // Return to the frontend for binding
url: secret.otpauth_url // Return the QR code source data
}
};
});
This endpoint sends the TOTP secret and QR code URL to the frontend for account binding.
At a minimum, the account model should add two fields: secret and otpauthUrl. When a user clicks “Rebind,” the system should strongly associate the latest secret with the current admin account to prevent the same dynamic code from being reused across multiple accounts.
{
"userName": "[email protected]",
"otpauthUrl": "otpauth://totp/SecretKey?secret=PU4WG5RDJVZVCSDHMM5UYSBFEMSEANBQ",
"secret": "PU4WG5RDJVZVCSDHMM5UYSBFEMSEANBQ"
}
This data structure shows that only minimal database field expansion is required to complete account binding.
AI Visual Insight: The image shows the admin interface used to generate and rebind TOTP secrets. The key elements include the plaintext secret, a QR code entry point, and the account binding workflow. This indicates that the product supports manually triggered secret rotation, which is well suited to admin-side security governance.
Supporting multiple authenticator options reduces 2FA rollout friction
The binding phase should not restrict users to a single client. A common approach is to allow users to scan the QR code with Authy, 2FAS, or a system-level authenticator app. This reduces training cost and avoids blocking adoption due to limited client choice.
AI Visual Insight: The image shows a standard TOTP token list in an authenticator app. Each account has a dynamically refreshing code, which demonstrates that after frontend binding is complete, the one-time password rotates automatically within each time window and does not require the server to push updates.
If the organization already operates within the WeChat ecosystem, a mini program can also serve as the authenticator. This approach is often more accessible for non-technical users, but the secret must still be stored securely to avoid leakage through client-side logs or caches.
AI Visual Insight: The image shows a mini program-based secondary verification tool, demonstrating that TOTP does not depend on a specific native app. Any carrier that can securely store the secret and generate time-based codes can act as the authentication endpoint.
A Feishu bot can serve as an auxiliary code retrieval channel, but it must be audited
The article also describes a Feishu helper bot: a user enters “security code email” in the bot conversation, the system looks up the account secret, calls speakeasy.totp(), and returns the dynamic code through the Feishu API. This is a typical example of integrating enterprise collaboration tools into an authentication workflow.
const account = await this.models.BackendUserAccount.findOne({ userName: email });
const token = speakeasy.totp({
secret: account.secret,
encoding: 'base32' // Specify the secret encoding to ensure correct generation
});
await this.messageService.sendTextMessage(chat_id, token); // Send the one-time code back to the chat window
This code generates a TOTP code in real time for an account through a Feishu bot.
AI Visual Insight: The image shows a Feishu bot returning a dynamic security code in a chat window. This indicates that the TOTP generation logic has been packaged as an enterprise IM capability, but it also exposes an authorization risk: anyone who can query a target account may retrieve its code. This design therefore requires query auditing, permission controls, and alerting.
Refactoring the login flow into two-step verification significantly reduces integration cost
The safest approach is not to rewrite the entire login system, but to add a second-stage verification step to the existing user/login endpoint. Step one still validates the username and password. If the 2FA policy applies, the server returns only f2a: true, and the frontend then prompts the user to enter the security code.
// Check whether F2A verification is enabled
const configContent = await services.tool.getConfigContent({
key: '1a26f4185f66d6f94ef3897f7a475305'
});
if (configContent && configContent.isOpen && configContent.whiteList.indexOf(userName) === -1) {
// Step 1: Only validate username and password; if no security code is provided,
// require the frontend to continue with step 2
if (!code) {
ctx.body = { f2a: true };
return;
}
const verify = speakeasy.totp.verify({
secret: account.secret,
encoding: 'base32',
token: code // Dynamic verification code entered by the user
});
if (!verify) {
ctx.status = 400;
ctx.body = { error: 'Invalid security code' };
return;
}
}
This verification logic handles three responsibilities: feature-flag control, whitelist bypass, and TOTP validation.
The recommended configuration model is a global switch plus whitelist: isOpen controls whether 2FA is enabled, and whiteList allows specific service accounts to bypass the check. This supports gradual rollout and also preserves compatibility for third-party systems or automation accounts.
{
"isOpen": false,
"whiteList": ["[email protected]"]
}
This configuration controls the scope of gradual 2FA enablement and is well suited to staged rollout in admin systems.
AI Visual Insight: The image shows the secondary verification modal in the login flow. It demonstrates that the frontend moves into a second phase after the username and password are validated, allowing the implementation to reuse the existing login page and API model as much as possible.
Transitional design determines whether two-factor authentication can be deployed smoothly
The biggest resistance to 2FA is usually not technical but organizational. Enabling it by force can lock out many users who have not yet bound a secret. Before full rollout, the system should provide a transition plan, binding instructions, and a remediation path so users can initialize their secrets in advance.
AI Visual Insight: The image shows feature guidance and transition messaging on the login page. It demonstrates that the product educates users before mandatory enforcement, which is a critical step for reducing login failures and support ticket volume.
More broadly, the strengths of this solution are clear: low invasiveness to the existing system, a mature algorithm, multiple client options, and support for gradual rollout through feature flags. Its limitations are also clear: secret custody, permissions for auxiliary code-retrieval channels, and incident recovery mechanisms still need improvement. For high-sensitivity admin systems, you should also add lost-device recovery, time-drift tolerance windows, encrypted secret storage, and audit alerting.
FAQ
Q1: Why is TOTP recommended for admin systems instead of SMS verification codes?
TOTP does not depend on carrier infrastructure, costs less, and avoids risks such as SMS interception and SIM-swapping attacks. For high-sensitivity scenarios like admin panels, it is generally more secure than SMS-based verification.
Q2: How should secret be stored more securely?
Do not store it as plaintext in a standard table field. A safer approach is to use server-side encryption, layered key management, least-privilege access, and operational auditing so that a database leak does not render 2FA ineffective.
Q3: Is retrieving codes through a Feishu bot suitable for production?
It can be used as a transitional or auxiliary capability, but you must restrict which accounts can be queried and record who queried which account’s dynamic code and when. Otherwise, it weakens the “second factor” back into a shared capability that others can retrieve on someone’s behalf.
Core summary: Based on a practical Node.js implementation, this article reconstructs a complete approach for integrating TOTP-based two-factor authentication into an admin system. It covers secret generation, account binding, secondary login verification, whitelist-based feature flags, and Feishu-assisted code retrieval, helping teams significantly improve admin security without heavily rewriting the existing login flow.