-
Notifications
You must be signed in to change notification settings - Fork 0
Tutorial A Simple Login System
Xyranaut edited this page Jun 1, 2026
·
3 revisions
This builds a minimal but complete register/login system step by step, so you see how every piece fits. It's deliberately smaller than the full mysql-admin demo — once you understand this, that demo will make sense.
You'll need: a running MySQL (Installing MySQL), the component installed, and a DB user (Getting started).
We'll build it in 6 steps. Copy each block into a filterscript (e.g. mylogin.pwn).
#include <open.mp>
#include <omp-mysql>
#define DB_HOST "127.0.0.1"
#define DB_USER "omp_app"
#define DB_NAME "mydb"
new MySQL:g_DB;
new bool:g_LoggedIn[MAX_PLAYERS]; // is this player authenticated?
new g_AccountId[MAX_PLAYERS]; // their DB row id
#define DIALOG_LOGIN 1
#define DIALOG_REGISTER 2public OnFilterScriptInit()
{
new MySQLConfig:cfg = mysql_config_create();
mysql_config_set(cfg, SSL_MODE, SSL_MODE_REQUIRED); // TLS on
g_DB = mysql_connect(DB_HOST, DB_USER, "${OMP_DB_PASS}", DB_NAME, cfg);
if (g_DB == MYSQL_INVALID_HANDLE)
{
print("[mylogin] DB connect failed");
return 1;
}
mysql_execute_sync(g_DB,
"CREATE TABLE IF NOT EXISTS users (\
id INT PRIMARY KEY AUTO_INCREMENT, \
name VARCHAR(24) NOT NULL UNIQUE, \
hash VARCHAR(255) NOT NULL)");
print("[mylogin] ready");
return 1;
}Set the password in your environment before starting the server:
export OMP_DB_PASS="your-password".
public OnPlayerConnect(playerid)
{
g_LoggedIn[playerid] = false;
g_AccountId[playerid] = 0;
new name[MAX_PLAYER_NAME];
GetPlayerName(playerid, name, sizeof name);
// prepared statement = safe even if the name has weird characters
new PreparedStatement:st = mysql_prepare(g_DB,
"SELECT id, hash FROM users WHERE name = ? LIMIT 1");
mysql_stmt_set_string(st, 1, name);
mysql_stmt_execute(st, "OnLookup", "d", playerid);
return 1;
}forward OnLookup(playerid);
public OnLookup(playerid)
{
new rows; mysql_rs_row_count(rows);
if (rows > 0)
{
// account exists -> remember its id and ask for the password
mysql_rs_get_int_by(0, "id", g_AccountId[playerid]);
ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD,
"Login", "Welcome back. Enter your password:", "Login", "Quit");
}
else
{
ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD,
"Register", "New here. Choose a password:", "Register", "Quit");
}
return 1;
}public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
if (dialogid == DIALOG_REGISTER)
{
if (!response) { Kick(playerid); return 1; } // ESC = leave
if (strlen(inputtext) < 4)
{
ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD,
"Register", "Too short (4+ chars):", "Register", "Quit");
return 1;
}
// hash the password (Argon2id) off the main thread, then insert
mysql_hash(inputtext, "OnHashed", "d", HASH_ARGON2ID, playerid);
return 1;
}
if (dialogid == DIALOG_LOGIN)
{
if (!response) { Kick(playerid); return 1; }
SetPVarString(playerid, "try_pw", inputtext);
// load the stored hash, then verify in OnVerifyLoaded
new PreparedStatement:st = mysql_prepare(g_DB,
"SELECT hash FROM users WHERE id = ?");
mysql_stmt_set_int(st, 1, g_AccountId[playerid]);
mysql_stmt_execute(st, "OnVerifyLoaded", "d", playerid);
return 1;
}
return 0;
}// REGISTER: hash is ready -> INSERT the new account
forward OnHashed(playerid, const hash[]);
public OnHashed(playerid, const hash[])
{
new name[MAX_PLAYER_NAME];
GetPlayerName(playerid, name, sizeof name);
new PreparedStatement:st = mysql_prepare(g_DB,
"INSERT INTO users (name, hash) VALUES (?, ?)");
mysql_stmt_set_string(st, 1, name);
mysql_stmt_set_string(st, 2, hash);
mysql_stmt_execute(st, "OnRegistered", "d", playerid);
return 1;
}
forward OnRegistered(playerid);
public OnRegistered(playerid)
{
g_AccountId[playerid] = mysql_rs_insert_id();
g_LoggedIn[playerid] = true;
SendClientMessage(playerid, 0x66FF66FF, "Registered and logged in!");
return 1;
}
// LOGIN: stored hash loaded -> verify the password the player typed
forward OnVerifyLoaded(playerid);
public OnVerifyLoaded(playerid)
{
new hash[255], pw[64];
mysql_rs_get_string_by(0, "hash", hash, sizeof hash);
GetPVarString(playerid, "try_pw", pw, sizeof pw);
DeletePVar(playerid, "try_pw");
if (mysql_verify_sync(pw, hash))
{
g_LoggedIn[playerid] = true;
SendClientMessage(playerid, 0x66FF66FF, "Logged in!");
}
else
{
SendClientMessage(playerid, 0xFF6666FF, "Wrong password.");
ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD,
"Login", "Wrong password. Try again:", "Login", "Quit");
}
return 1;
}What you just used:
- TLS connection (mandatory),
- a prepared statement for the lookup, login load, and INSERT (injection-safe),
-
Argon2id hashing (
mysql_hash) and verification (mysql_verify_sync), - callbacks to stay async (no server lag),
- cancel-the-dialog = kicked (so login is mandatory).
- Wipe
g_LoggedIn[playerid]onOnPlayerDisconnect(so a reused slot isn't auto-logged-in). - Block commands/actions until
g_LoggedIn[playerid]is true; add a login timeout. - Use
SSL_MODE_VERIFY_CAwith a real CA in production. - See the full mysql-admin demo for all of this done properly, plus saving position/money/etc., admin levels, and RCON tools.
Stuck? See Troubleshooting.
Understand
Use
- Installing MySQL
- Docker Compose
- Getting started
- Configuration
- SQL crash course
- Designing your tables
- Storing game data
- Dates & times
- First queries
- Async patterns
- Reading results
- Prepared statements
- Passwords & hashing
- Transactions
- Models (active-record)
- Tutorial: login system
- mysql-admin demo
Deeper
Reference