aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Metin2

Metin2 Register System: A Secure PHP Signup Form

When you launch a Metin2 server, the first door players walk through is the signup form on your website. A well-built Metin2 register system does more than grab a username and password; it validates input, stores the password in the exact format the game expects, and writes to the account table safely. In this guide we build a working, injection-proof registration flow from scratch with PHP and PDO.

How the account table works

In classic Metin2 sources, membership data lives in the account table inside a database called account. The game core (game and db processes) authenticates logins directly against this table. The columns we care about are:

  • login — the username (usually short and unique).
  • password — the field the game reads. The classic source expects the 41-character hash produced by MySQL's PASSWORD() function.
  • social_id — a 7-digit security code (a second password) used for actions such as deleting characters.
  • email and create_time — contact details and registration time.

One critical detail: the PASSWORD() function was removed in MySQL 8.0. Most Metin2 servers run MariaDB or MySQL 5.x, so the function still exists, but you must check your version. You are forced to store the password with the same algorithm the game core uses — otherwise a player who registers on the web cannot log into the game.

A secure database connection

Above all, use PDO with prepared statements. By separating user input from the query, you stop SQL injection at the source. Keep the connection in a separate config.php file:

<?php
// config.php
$host = '127.0.0.1';
$db   = 'account';
$user = 'metin2_web';      // user with rights only on the account table
$pass = 'STRONG_PASSWORD';

$dsn = "mysql:host=$host;dbname=$db;charset=utf8mb4";
$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
]);

Create a dedicated MySQL user for the web and grant it only the privileges it needs on account.account. Even if the web server is compromised, the attacker should not reach your entire game database.

The registration form and input validation

Start with a simple but sufficient HTML form. The real work is validating the incoming data on the server side — never trust client-side checks.

<form method="post" action="register.php">
  <input name="login" placeholder="Username" required>
  <input name="email" type="email" placeholder="Email" required>
  <input name="password" type="password" placeholder="Password" required>
  <input name="password2" type="password" placeholder="Repeat password" required>
  <input type="hidden" name="csrf" value="<?= $_SESSION['csrf'] ?>">
  <button type="submit">Sign up</button>
</form>

On the server side, check each field one by one:

<?php
session_start();
require 'config.php';

// CSRF check
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
    http_response_code(400);
    exit('Invalid request.');
}

$login = trim($_POST['login'] ?? '');
$email = trim($_POST['email'] ?? '');
$pw    = $_POST['password'] ?? '';
$pw2   = $_POST['password2'] ?? '';
$errors = [];

if (!preg_match('/^[a-zA-Z0-9]{4,16}$/', $login)) {
    $errors[] = 'Username must be 4-16 letters/digits only.';
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $errors[] = 'Enter a valid email.';
}
if (strlen($pw) < 6 || $pw !== $pw2) {
    $errors[] = 'Password must be at least 6 chars and both fields must match.';
}

Restricting login to a strict pattern (letters and digits only) prevents both compatibility issues with the game client and unexpected characters.

Check for username collisions

To stop two people from registering with the same username, query before you write. Use a prepared statement here too:

if (!$errors) {
    $stmt = $pdo->prepare('SELECT id FROM account WHERE login = ? LIMIT 1');
    $stmt->execute([$login]);
    if ($stmt->fetch()) {
        $errors[] = 'That username is already taken.';
    }
}

Still, don't make this your only line of defense: add a UNIQUE index on the login column of the account table. If two requests arrive at the same moment, the database-level constraint blocks the duplicate.

Hash the password and write to the account table

In the final step we create the record. Hashing the password inside the SQL with PASSWORD() guarantees the format the game expects. Generate a random 7-digit code for social_id:

if (!$errors) {
    $social = sprintf('%07d', random_int(0, 9999999));

    $sql = 'INSERT INTO account (login, password, social_id, email, create_time)
            VALUES (?, PASSWORD(?), ?, ?, NOW())';
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$login, $pw, $social, $email]);

    header('Location: register.php?ok=1');
    exit;
}

Note that we pass the plain password as a parameter and let MySQL do the hashing. This keeps prepared-statement safety intact while making the hash byte-for-byte compatible with the game. If you hash the password yourself in PHP (for example with password_hash()) and store that, the game core will not recognize it — a very common mistake. password_hash()/bcrypt only makes sense if you are building a purely web-based account system that is independent of the game.

Before going to production

Once the flow works, add these security layers:

  • Rate limiting: block more than a few registrations per minute from the same IP to slow down bots.
  • Force HTTPS: passwords must never travel in plain text.
  • CAPTCHA: add hCaptcha/reCAPTCHA against automated signups.
  • Error masking: never show technical errors to the user; log details server-side.

Frequently Asked Questions

Why hash the password in MySQL instead of PHP?

Because the Metin2 game core compares the login password against the hash produced by MySQL's PASSWORD() function. If you don't use the same algorithm, a player who registers on the web cannot enter the game. Unless your web and game are separate systems, matching is mandatory.

What if my server doesn't have the PASSWORD() function?

MySQL 8.0 removed it. The fix is to move to MariaDB or MySQL 5.7 (which most Metin2 setups already use), or to map whatever hash your core supports (some forks ship modified schemas). In every case, the reference is your game core's auth code.

What is social_id for, can I leave it empty?

social_id is the in-game security code (a second password), asked for during critical actions like deleting a character. Rather than leaving it empty, the safest approach is to assign a random value at registration and let the player change it from their panel.

Need a secure register and account panel for your Metin2 server? I can build it end to end with validation, email confirmation and panel integration — get in touch.

Bu kategorideki tüm yazılar →

Devamı için