By PistonmasterUpdated 6 min read

Testing Minecraft Authentication Systems - Security and Login Flow

Learn how to test authentication plugins like AuthMe, validate Microsoft/Mojang auth, and ensure secure login flows on your Minecraft server

minecraftauthenticationauthmesecurityoffline-modelogin

Understanding Minecraft Authentication

Minecraft has two authentication modes built in. Plugins bolt password-based security onto the weaker one.

Online vs Offline Mode

Online mode (online-mode=true in server.properties) is the default and the one you want. The server talks to Mojang/Microsoft, confirms the player actually owns the account, and assigns real UUIDs. No plugins needed, no impersonation possible. The only downside: Mojang's servers have to be reachable.

Offline mode (online-mode=false) skips all of that. Anyone can connect with any username -- there's zero verification. That's fine for local dev or behind a proxy like BungeeCord that handles auth upstream, but it's a disaster on a public server. Without verification, UUIDs are generated from the username alone, meaning anyone who types "Notch" gets Notch's UUID. Permissions, inventories, OP status -- all of it is tied to that UUID.

Never run offline mode on a public server without an authentication plugin. Without one, any player can join as any username, including admin accounts. This is the single most common way servers get compromised.

Plugin-Based Authentication

For offline mode servers, authentication plugins add password-based login:

PluginApproachBest For
AuthMePassword registration/loginPublic offline servers
FastLoginHybrid online/offline detectionMixed premium + cracked players
nLoginSimple password authSmall servers

AuthMe Configuration

AuthMe is the most widely used auth plugin. Drop the JAR into your plugins/ folder and restart; it generates its config on first boot.

Core Settings

plugins/AuthMe/config.yml
settings:
  sessions:
    enabled: true
    timeout: 10        # Minutes before requiring re-login
    ipCheck: true      # Bind sessions to IP

registration:
  enabled: true
  force: true
  messageInterval: 5
  maxRegPerIp: 1       # Prevent spam accounts

security:
  minPasswordLength: 5
  maxPasswordLength: 30
  passwordHash: 'BCRYPT2Y'
  bCryptLog2Rounds: 10
  maxLoginTries: 3
  tempBanLength: 5     # Minutes after max failed attempts
  unsafePasswords:
    - '123456'
    - 'password'
    - 'qwerty'

timeout:
  kick: 30             # Seconds before kicking unauthenticated players

restrictions:
  allowCommands:
    - '/login'
    - '/register'
    - '/l'
    - '/reg'
  allowMovement: false
  allowChat: false

Always use BCRYPT or BCRYPT2Y for password hashing. SHA256 is vulnerable to rainbow table attacks and should never be used for password storage.

Database Backend

SQLite will choke on concurrent logins -- use MySQL for anything real. Even 20 players logging in after a restart can saturate SQLite's single-writer lock.

plugins/AuthMe/config.yml
DataSource:
  backend: 'MYSQL'
  mySQLHost: 'localhost'
  mySQLPort: '3306'
  mySQLDatabase: 'minecraft_authme'
  mySQLUsername: 'authme_user'
  mySQLPassword: 'secure_password_here'
  mySQLTablename: 'authme'
  mySQLPoolSize: 10
setup.sql
CREATE DATABASE minecraft_authme;
CREATE USER 'authme_user'@'localhost' IDENTIFIED BY 'secure_password_here';
GRANT ALL PRIVILEGES ON minecraft_authme.* TO 'authme_user'@'localhost';
FLUSH PRIVILEGES;

Testing Authentication

Here's how to make sure your auth setup actually works.

Registration and Login

Connect with a fresh account. You'll land frozen in place -- no movement, no chat, no commands except /register and /login. That's the baseline. Run /register <password> <password>, and now you can move and interact normally. Disconnect, reconnect, and you're frozen again until you /login.

If sessions are enabled, reconnecting within the timeout window (default 10 minutes) auto-logs you in. Wait longer than that, and it'll ask for your password again.

Password Validation

Try the obvious bad passwords first. 123456, password, qwerty -- anything on your unsafePasswords list gets rejected. Same for using your username as your password; AuthMe blocks that too. Anything shorter than minPasswordLength also fails.

Then test the change flow: register, run /changepassword oldpass newpass, disconnect, reconnect, and confirm the old password no longer works. Only the new one gets you in.

Brute Force Protection

This one's straightforward. Enter the wrong password three times with maxLoginTries: 3. On the third failure, the server kicks you with a "too many attempts" message. Try connecting again during the tempBanLength window -- you'll get rejected before you even see the login prompt.

Restriction Enforcement

Before logging in, check that all of these are blocked:

  • Movement (if allowMovement: false)
  • Chat messages (if allowChat: false)
  • Commands other than those in allowCommands

Also test the kick timeout: join, sit there doing nothing, and after the configured seconds the server boots you. If it doesn't, your timeout.kick value isn't being applied.

Database Persistence

Register an account, stop the server, restart it, and log in. The account persists across restarts. Verify the entry in your database:

verify.sql
SELECT username, ip, regdate FROM authme WHERE username = 'TestPlayer';

If the row's missing, your database backend isn't configured correctly -- check that AuthMe is actually writing to MySQL and not falling back to SQLite.

Case-Sensitivity and Impersonation

Here's one people miss: if a player registers as "Notch", can someone else join as "notch"? Without preventOtherCase: true, AuthMe treats them as separate accounts -- which means impersonation is wide open. Turn it on:

plugins/AuthMe/config.yml
settings:
  preventOtherCase: true

With this set, "notch" gets rejected when "Notch" is already registered.

Hybrid Authentication with FastLogin

FastLogin sits in front of AuthMe and automatically detects premium accounts. Premium players skip the password flow entirely; cracked players fall through to AuthMe's /register and /login.

Test three scenarios:

  1. Premium account -- auto-logs in with no password prompt
  2. Cracked account -- goes through the normal AuthMe registration and login
  3. Cracked client using a premium username -- gets rejected to prevent impersonation

Security Testing

SQL Injection

On your own test server, try registering with payloads like /register ' OR '1'='1 password. AuthMe escapes these. If any succeed, you're running a dangerously outdated version -- update immediately.

Timing Attacks

Run /login ExistingUser wrongpass and /login FakeUser wrongpass and compare response times. Modern AuthMe uses constant-time comparison, so both respond in roughly the same time. A measurable difference leaks whether an account exists.

Session Security

With ipCheck: true, a session token from one IP can't be reused from a different one. Log in, note the session, then try to resume it from a different address. The server forces re-authentication. Without this, stolen session tokens work from anywhere -- which is how account hijacking happens on poorly configured servers.

Load Testing with Bots

Manual testing covers correctness, but concurrency bugs and performance problems won't show up without real load.

What to Test

Mass registration: Connect 50 bots at once, each registering a unique account. Every one of them needs to succeed -- no race conditions, no duplicate key errors in the database.

Login throughput: Disconnect all 50, then reconnect them simultaneously. Each runs /login. Watch for timeouts, failed logins, or "too many connections" errors from your database.

Session timeout accuracy: Log in a bot, disconnect, reconnect within the session window, and confirm it auto-logs in. Reconnect after the window expires -- this time it asks for a password.

What to Monitor

terminal
# Auth events in real time
tail -f logs/latest.log | grep -i "authme\|login\|register"

# Database connection count (MySQL)
mysql -e "SHOW PROCESSLIST;"

# Server TPS under load
/spark tps

If your auth system handles 100 concurrent logins without noticeable TPS drops, you're in good shape. If you see timeouts or "too many connections" errors, bump your connection pool or switch from SQLite to MySQL.

Things People Get Wrong

Running offline mode "just for now." There's no such thing as temporary on a public server. Someone will find it. Stick with online mode if you can -- it's simpler, more secure, and you don't have to maintain an auth plugin at all. If you genuinely need offline mode (proxy setups, cracked player support), lock it down with AuthMe before you open the port.

Using SHA256 for password hashing. BCRYPT2Y exists. Use it. SHA256 is fast by design, which is exactly what you don't want for password storage -- it makes brute forcing trivial.

Leaving ipCheck off. Session tokens without IP binding are reusable from anywhere. Turn it on. The only reason to leave it off is if your players are on connections that change IP constantly, and even then you're making a tradeoff.

No registration cap per IP. Without maxRegPerIp, a single person can create hundreds of accounts. Set it to 1 or 2. If someone legitimately needs more, you can whitelist them.

Skipping load testing. Everything works fine with 5 players. Then 50 log in after a restart and your SQLite database locks up, AuthMe starts timing out, and half your players get kicked. Test under load before you go live. Race conditions and connection pool exhaustion only show up when it matters.

Test your auth the way an attacker would -- with automation, edge cases, and bad input -- and you'll know it holds up before your players find out the hard way.