Passwordless Admin Login With Messenger One-Time Codes
Published 2026-07-08 · SameOS Tools
A personal server admin page is usually guarded by a single password. The problems: you have to keep remembering it, and one leak means game over. On a site I operate, I removed the fixed password entirely. Instead, pressing "Send code" on the login screen delivers a one-time code to my messenger channel, and only that code gets you in. It works like the SMS codes your bank sends, except all you need is a webhook (a dedicated URL that lets an outside program post messages into a messenger channel - free to create on Discord, Slack, and others), with no paid SMS service.
How It Works
The core equation is: whoever can read the code = the owner of the messenger channel = the admin. When the button is pressed, the server generates a random code, sends the plaintext only to the admin's messenger, and stores only a hash (a scrambled value that cannot be reversed - even if the server is compromised, the code cannot be recovered). Even if an attacker discovers the admin URL, they cannot see the messenger where the code arrives, so they cannot get in. The messenger account effectively becomes a second factor.
Four safety rules go with it: the code expires in 10 minutes, is destroyed immediately after one use, is invalidated after 5 wrong attempts, and can be re-sent at most once every 30 seconds. Add a 2-second delay on every wrong attempt and guessing the number by brute force becomes impractical. Also send a notification on every successful login - if a login you did not make ever happens, you know instantly.
Things to Watch
First, if the webhook breaks you cannot receive codes and lock yourself out, so keep an empty emergency-password slot in the config file as an escape hatch. Second, set httponly and samesite on the session cookie that keeps you logged in (browser safeguards that stop malicious scripts from stealing it). Third, some webhook providers reject the default Python User-Agent with 403 - always set a User-Agent header. Fourth, keep the admin URL hard to guess and never list it in robots.txt (the file that tells search engines about your site structure) - listing it publishes the address for anyone to see.
Practical example code
// 1) Issue a code - the "Send code" button (30s resend limit)
if (isset($_POST['send_code'])) {
if ((int)get_setting('otp_sent_at') > time() - 30) {
$notice = 'Code just sent. Try again in 30 seconds.';
} else {
$code = str_pad((string)random_int(0, 99999999), 8, '0', STR_PAD_LEFT);
set_setting('otp_hash', password_hash($code, PASSWORD_DEFAULT)); // store the hash only
set_setting('otp_exp', (string)(time() + 600)); // valid for 10 minutes
set_setting('otp_tries', '0');
set_setting('otp_sent_at', (string)time());
send_webhook("Admin one-time code: {$code} (valid 10 min, single use)");
}
}
// 2) Verify the code - expiry, attempts, and hash must all pass
if (isset($_POST['code'])) {
$ok = get_setting('otp_hash') !== ''
&& (int)get_setting('otp_exp') > time()
&& (int)get_setting('otp_tries') < 5
&& password_verify(trim($_POST['code']), get_setting('otp_hash'));
if ($ok) {
session_regenerate_id(true); // prevent session fixation
$_SESSION['admin'] = true;
set_setting('otp_hash', ''); // single use - destroy immediately
send_webhook('Admin login - IP ' . $_SERVER['REMOTE_ADDR']);
} else {
set_setting('otp_tries', (string)((int)get_setting('otp_tries') + 1));
sleep(2); // slow down brute force
}
}
// 3) Send the webhook (Discord - User-Agent header required)
function send_webhook(string $msg): void {
$ctx = stream_context_create(['http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\nUser-Agent: my-server/1.0",
'content' => json_encode(['content' => $msg], JSON_UNESCAPED_UNICODE),
'timeout' => 10,
]]);
@file_get_contents(WEBHOOK_URL, false, $ctx);
}
// 4) Harden the session cookie (before session_start)
session_set_cookie_params([
'httponly' => true, 'samesite' => 'Lax', 'secure' => true,
]);
The best parts: there is no password to remember, and every login attempt leaves a trace in your messenger. Webhook-based server status alerts and private file protection are covered in separate articles.