How to protect your MU Online server's admin panel with two-factor authentication (2FA)
Implement TOTP-based two-factor authentication (2FA) in your MU Online server's PHP admin panel, with secret generation, QR Code, code verification, and an access recovery plan in case the player loses their device.
The admin panel of a MU Online server is an attacker's most valuable target: a single compromised Administrator-level account can generate items, alter balances, ban players, or take down the entire database. Protecting this access with just a password — even a strong one — leaves the server vulnera
The admin panel of a MU Online server is an attacker's most valuable target: a single compromised Administrator-level account can generate items, alter balances, ban players, or take down the entire database. Protecting this access with just a password — even a strong one — leaves the server vulnerable to phishing, password reuse from another site's breach, and keyloggers. TOTP-based two-factor authentication (2FA) adds a second barrier that an attacker can't reproduce with just the stolen password. This tutorial shows how to implement 2FA in the server's legacy PHP panel, from the database side to the player's verification screen.
Why a password alone isn't enough
Passwords leak through several paths that have nothing to do with the password's strength: reuse of the same password on another site that suffered a breach, phishing disguised as an identical-looking login page, or keylogger malware on the administrator's machine. In any of these cases, the attacker gets the correct password and passes authentication normally. 2FA breaks this scenario because it requires a second factor — something the administrator has (the code generated on the phone), not just something they know (the password).
How TOTP (Time-based One-Time Password) works
TOTP generates a 6-digit numeric code that changes every 30 seconds, calculated from a secret shared between the server and the authenticator app (Google Authenticator, Authy, Microsoft Authenticator) and the current time. The server and the phone never exchange this code over the network at generation time — both calculate the same value independently, which makes the method resistant to network interception.
| Component | Role |
|---|---|
| Shared secret | Generated once, stored in the database and in the user's app |
| Current timestamp | Synced between server and device (30s window) |
| HMAC-SHA1 algorithm (RFC 6238) | Combines secret + timestamp to generate the 6-digit code |
| Tolerance window | Accepts the code from the previous/next interval to tolerate small clock drift |
Step 1 — Prepare the secrets table in the database
Add a column to store each admin account's TOTP secret, and a separate table for recovery codes:
ALTER TABLE admin_accounts ADD COLUMN totp_secret VARCHAR(64) NULL;
ALTER TABLE admin_accounts ADD COLUMN totp_enabled TINYINT(1) NOT NULL DEFAULT 0;
CREATE TABLE admin_recovery_codes (
id INT AUTO_INCREMENT PRIMARY KEY,
admin_id INT NOT NULL,
code_hash VARCHAR(255) NOT NULL,
used TINYINT(1) NOT NULL DEFAULT 0,
FOREIGN KEY (admin_id) REFERENCES admin_accounts(id)
);
Never store the TOTP secret in publicly accessible plain text, and never store recovery codes without hashing them — treat them like single-use passwords.
Step 2 — Install a TOTP library in PHP
Use a well-established open source library instead of implementing the algorithm from scratch:
composer require spomky-labs/otphp
This avoids subtle RFC 6238 implementation bugs that could generate codes incompatible with standard authenticator apps.
Step 3 — Generate the secret and the activation QR Code
use OTPHP\TOTP;
$totp = TOTP::generate();
$totp->setLabel('PainelAdmin-ViciadosMU');
$totp->setIssuer('ViciadosMU');
$secret = $totp->getSecret();
// Save $secret (encrypted) in the logged-in admin's totp_secret column
$qrCodeUrl = $totp->getQrCodeUri(
'https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=[DATA]',
'[DATA]'
);
// Display $qrCodeUrl on screen for the admin to scan with the authenticator app
Show the QR Code only once, on the activation screen, and never log the secret in an application log — it's equivalent to a master password for the second factor.
Step 4 — Verify the code at activation before confirming
Before setting totp_enabled = 1, require the administrator to type the code generated by the app, confirming the QR Code was scanned correctly:
$codigoDigitado = $_POST['codigo'];
if ($totp->verify($codigoDigitado)) {
// Activate totp_enabled = 1 in the database
// Generate and display recovery codes (one time only)
} else {
// Error: invalid code, do not activate yet
}
This step avoids the scenario where the administrator thinks they activated 2FA, but the app wasn't configured correctly, and they get locked out on the next login.
Step 5 — Generate and display recovery codes
Upon successful activation, generate 8 to 10 single-use codes and display them only once, instructing the administrator to store them offline:
function gerarCodigosRecuperacao(int $quantidade = 10): array {
$codigos = [];
for ($i = 0; $i < $quantidade; $i++) {
$codigos[] = strtoupper(bin2hex(random_bytes(4))); // e.g.: A1B2C3D4
}
return $codigos;
}
// Save only the hash of each code in the admin_recovery_codes table
foreach ($codigos as $codigo) {
$hash = password_hash($codigo, PASSWORD_BCRYPT);
// INSERT INTO admin_recovery_codes (admin_id, code_hash) VALUES (?, ?)
}
Step 6 — Require the second factor at login
In the login flow, after the password is validated correctly, show the TOTP code screen before granting the full admin session:
if ($senhaValida) {
if ($admin['totp_enabled']) {
// Redirect to the code verification screen,
// without yet granting an administrative-level session
$_SESSION['pending_2fa_admin_id'] = $admin['id'];
} else {
// Grant the session normally (or force 2FA activation, if mandatory)
}
}
// On the verification screen
$codigo = $_POST['codigo'];
$totp = TOTP::createFromSecret($segredoDoBanco);
if ($totp->verify($codigo)) {
// Grant the full administrative session
} else {
// Check whether it's a valid recovery code (hash match and used = 0)
// Otherwise, deny access and log the attempt
}
Step 7 — Define the mandatory-use policy by account level
Not every account needs the same level of rigor. Set the policy according to the access level's risk:
| Account level | 2FA mandatory? | Justification |
|---|---|---|
| Owner | Yes | Full access, greatest possible damage if compromised |
| Administrator | Yes | Sensitive commands (additem, setmoney, ban) |
| Event moderator | Recommended | Limited access, but still distributes items |
| Junior support | Optional | Lower economic-impact commands |
| Regular player account | Optional/encouraged | Personal account protection, not administrative |
Step 8 — Recovery plan without compromising security
When an administrator loses their device and doesn't have recovery codes, define a manual, documented process instead of simply disabling 2FA on an informal Discord request:
- Require out-of-band identity verification (video call with another trusted administrator, or confirmation via a previously registered email).
- A second administrator (never the requester themselves) resets
totp_enabled = 0directly in the database. - Force 2FA re-activation immediately on the next login.
- Log the incident internally, including who authorized the reset.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| App code doesn't match the server | Phone or server clock out of sync | Sync NTP on the server and check the phone's time |
| Admin lost access and has no recovery codes | Codes never generated or not saved at activation | Implement a manual reset process with cross-verification |
| TOTP secret exposed in application logs | Debug/logging capturing the sensitive variable | Remove secret variables from logs and rotate exposed secrets |
| 2FA enabled but the session never asks for the second factor | Login flow doesn't check totp_enabled correctly | Review pending-session logic before granting full access |
| 2FA reset performed with no identity verification | No documented recovery process | Formalize the process with a mandatory second administrator |
2FA implementation checklist for the panel
- TOTP secret and recovery codes table created in the database.
- Trusted TOTP library integrated (e.g., spomky-labs/otphp).
- Activation flow with QR Code and code verification before confirming.
- Recovery codes generated, hashed, and shown only once.
- Login requiring the second factor before granting a full admin session.
- Mandatory-use policy defined by account level.
- Documented access recovery process with cross-verification.
With the panel protected by 2FA, also review the rest of the admin team's access controls — the MU Online server setup guide covers the infrastructure foundation this extra security layer should be built on.
Frequently asked questions
Is SMS 2FA secure enough for the admin panel?
It's better than nothing, but SMS is vulnerable to SIM swapping and interception. For Administrator and Owner-level accounts, prefer TOTP (Google Authenticator, Authy) or a physical key (FIDO2/WebAuthn), keeping SMS at most as a secondary recovery method.
What happens if the administrator loses the phone with the authenticator?
That's why it's essential to generate and store recovery codes (backup codes) at the moment 2FA is activated, kept somewhere safe offline. Without them, the only option is a manual identity verification process to reset 2FA directly in the database.
Should 2FA be mandatory for every panel account level?
It's recommended as mandatory for levels with sensitive administrative commands (Admin, Owner) and optional but encouraged for regular player accounts. Making it mandatory for everyone at once can generate support friction if the community isn't prepared.
Does TOTP work without internet on the administrator's phone?
Yes. The TOTP algorithm generates the code locally from the shared secret and the device's clock, with no need for an internet connection at the moment of generation — only the phone's clock needs to be properly synced.
Do I need a paid library to implement TOTP in PHP?
No. Open source libraries like spomky-labs/otphp or simple implementations of the RFC 6238 algorithm can be integrated into the existing PHP panel for free, with no dependency on a paid external service.