Auth Plugin
The auth plugin adds password-based authentication to offline-mode proxies. When a player joins a server that uses it, they're held in a limbo state and must /register (first visit) or /login (returning player) before reaching the backend server.
Players with a paid Minecraft account (premium) skip this. When premium auto-login is enabled, the plugin queries the Mojang API and triggers the Mojang encryption handshake (RSA + hasJoined). If the client proves ownership, it goes straight to the backend.
Activation
The plugin is compiled in by default (plugin-auth feature), but it only holds players on the servers that name its limbo handler. Add "auth" to limbo_handlers in each client_only or offline server config that should require a login:
proxy_mode = "offline"
limbo_handlers = ["auth"]2
Players joining any other server go straight to the backend. The forwarding modes (passthrough, zero_copy, server_only) have no limbo, so the plugin cannot hold players there.
How it works
Offline (cracked) players
- A player connects to a server that lists
"auth"inlimbo_handlersand enters the limbo state. - The plugin checks if an account exists for that username (case-insensitive).
- New players see
/register <password> <confirm>. Returning players see/login <password>. - A title and chat message remind the player what to do. Reminders repeat on an interval.
- On success, the player leaves limbo and reaches the backend server.
- On failure, the player gets feedback with remaining attempts. After too many failures, they're kicked.
- If the player doesn't authenticate within the timeout, they're disconnected.
Premium players
When [premium] enabled = true:
- A player connects. Before entering limbo, a
PreLoginEventfires. - The plugin checks the Mojang API (
api.mojang.com) to see if the username belongs to a paid account. - If premium, the proxy forces the Mojang encryption handshake (RSA key exchange +
sessionserver.mojang.com/session/minecraft/hasJoined). - If the client proves it owns the account, the player's profile arrives with signed skin textures.
- The auth handler sees the signed textures and returns
Accept, skipping/loginand/register. - If the client fails the encryption handshake (cracked client using a premium username), it gets disconnected. The plugin remembers the failure: by default the name's next connections are refused, and with
premium_name_conflict_action = "allow_cracked"they fall back to/loginor/register. See the second attempt.
TIP
In client_only proxy mode, premium players are already Mojang-authenticated by the proxy core. The auth handler sees signed textures and skips the limbo with no API call. Cracked players get kicked on first connection. One whose name is not premium joins on their second attempt via the remember mechanism; one using a premium name only does with premium_name_conflict_action = "allow_cracked".
Configuration
The plugin stores its config in plugins/auth/config.toml. On first run, it creates the file with defaults.
Storage
[storage]
backend = "json"
path = "accounts.json"
auto_save_interval_seconds = 3002
3
4
backend only supports "json" for now. Accounts are saved to plugins/auth/accounts.json. The plugin writes to disk on a periodic interval and on shutdown, using an atomic write (temp file + rename) to avoid corruption.
Password hashing
[hashing]
argon2_memory_cost = 19456
argon2_time_cost = 2
argon2_parallelism = 1
migrate_legacy_hashes = true2
3
4
5
Passwords are hashed with Argon2id. If migrate_legacy_hashes is true, bcrypt hashes from older setups are re-hashed to Argon2id on the next successful login.
The plugin generates a dummy hash at startup. When a player tries /login with a username that doesn't exist, the plugin runs a full Argon2 verify against the dummy hash. This prevents attackers from measuring response times to determine which usernames have accounts.
Password policy
[password_policy]
min_length = 8
max_length = 128
blocked_passwords_file = "blocked_passwords.txt"
check_username = true2
3
4
5
blocked_passwords_file points to a text file (one password per line) in the plugins/auth/ directory. If the file doesn't exist, the blocklist is disabled. check_username rejects passwords that match the player's username.
Security
[security]
max_login_attempts = 5
login_timeout_seconds = 60
title_reminder_interval_seconds = 52
3
4
After max_login_attempts wrong passwords, the player is kicked. Set login_timeout_seconds to 0 to disable the timeout. Set title_reminder_interval_seconds to 0 to disable periodic title reminders.
Admin
[admin]
admin_usernames = []2
Usernames listed here can use admin commands (/forcelogin, /forceunregister, /forcechangepassword). Usernames are case-insensitive. Players with the infrarust.admin permission also have admin access, regardless of this list.
Premium auto-login
[premium]
enabled = false
cache_ttl_seconds = 600
rate_limit_per_second = 1
rate_limit_action = "allow_offline"
lookup_error_action = "allow_offline"
premium_name_conflict_action = "kick"
allow_cracked_command = true
failed_auth_remember_seconds = 6002
3
4
5
6
7
8
9
| Option | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable premium auto-login detection |
cache_ttl_seconds | u64 | 600 | How long to cache Mojang API results (seconds) |
rate_limit_per_second | u32 | 1 | Max Mojang API lookups per second |
rate_limit_action | string | "allow_offline" | What to do when the rate limit is hit |
lookup_error_action | string | "allow_offline" | What to do when a Mojang lookup fails (network error, bad status, malformed response) |
premium_name_conflict_action | string | "kick" | What to do when a cracked client uses a premium name |
allow_cracked_command | bool | true | Register /cracked and /premium commands |
failed_auth_remember_seconds | u64 | 600 | How long to remember a failed premium auth (see below) |
rate_limit_action values:
| Value | Behavior |
|---|---|
"allow_offline" (default) | Let the player through in offline mode. They see /login or /register as usual. |
"deny" | Reject the connection. |
lookup_error_action takes the same values and governs Mojang lookup failures other than rate limiting (network errors, unexpected status codes, malformed responses). With "deny", the player is disconnected with the lookup_failed message.
premium_name_conflict_action values:
| Value | Behavior |
|---|---|
"kick" (default) | The cracked client fails the Mojang encryption handshake and gets disconnected. The plugin remembers the failure for failed_auth_remember_seconds and refuses the name's connections until then with the premium_name_conflict message. |
"allow_cracked" | After the failed handshake, the name's connections within failed_auth_remember_seconds skip online authentication and fall back to the normal /login / /register flow. |
"allow_cracked" lets a cracked client play under a premium player's name: it needs that account's password, or registers the name itself while nobody has. With "kick", a premium player whose session check failed once, for example with an expired launcher session, is refused the same way until failed_auth_remember_seconds runs out. Neither setting applies when the Mojang lookup itself fails and rate_limit_action or lookup_error_action lets the player through: the name was never found to be premium, so the player gets /login or /register.
WARNING
The Mojang API has a rate limit of roughly 600 requests per 10 minutes. The rate_limit_per_second setting is a local governor to stay under that limit. On a high-traffic server with many first-time players, cached results handle most lookups, but a burst of new players will trigger the rate limit. The default "allow_offline" action ensures nobody is locked out.
Premium messages
[premium.messages]
premium_login = "&aWelcome back, {username}! (Premium auto-login)"
premium_name_conflict = "&cThis username belongs to a premium account. Use the official Minecraft launcher."
cracked_enabled = "&aYou will now login as a cracked player. Reconnect to apply."
cracked_disabled = "&aYou will now login as a premium player. Reconnect to apply."
rate_limited = "&cThe server is busy. Please try again in a moment."
lookup_failed = "&cCould not verify your account status. Please try again later."2
3
4
5
6
7
premium_name_conflict is the reason given to a connection refused under premium_name_conflict_action = "kick".
Messages
Every message the plugin sends is configurable. Messages support Minecraft color codes (&a, &c, &7, etc.) and placeholders like {username}, {attempts_left}, {max_attempts}, {min_length}, {max_length}.
[messages]
login_title = "&6Authentication Required"
login_subtitle = "&7/login <password>"
login_success = "&aLogin successful!"
login_fail = "&cWrong password! &7({attempts_left}/{max_attempts} attempts left)"
login_max_attempts = "&cToo many failed login attempts."
login_timeout = "&cAuthentication timed out."
register_title = "&6Welcome, {username}!"
register_subtitle = "&7/register <password> <confirm>"
register_success = "&aAccount created successfully!"
register_password_mismatch = "&cPasswords do not match."
register_password_too_short = "&cPassword must be at least {min_length} characters."
register_password_too_long = "&cPassword must be at most {max_length} characters."
register_password_is_username = "&cPassword cannot be the same as your username."
register_password_blocked = "&cThat password is too common. Please choose a different one."
register_account_exists = "&cAn account already exists for this username."
reminder_title = "&6Please authenticate"
reminder_subtitle = "&7Use /login or /register"
unknown_command = "&7Available commands: &f/login&7, &f/register"2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
See the default config for the full list, which also covers /changepassword, /unregister, and admin command messages.
Commands
Player commands
| Command | Aliases | Description |
|---|---|---|
/login <password> | /l | Authenticate with an existing account |
/register <password> <confirm> | /reg | Create a new account |
/changepassword <old> <new> | /changepw, /cp | Change your password |
/unregister <password> | Delete your account | |
/cracked | Switch to cracked mode (use /login instead of auto-login) | |
/premium | Switch back to premium auto-login |
/cracked and /premium are only registered when [premium] enabled = true and allow_cracked_command = true. Both require the player to reconnect for the change to take effect.
Admin commands
These require either the infrarust.admin permission or a username in admin_usernames.
| Command | Description |
|---|---|
/forcelogin <username> | Force-authenticate a player stuck in auth limbo |
/forceunregister <username> | Delete another player's account |
/forcechangepassword <username> <password> | Reset another player's password |
Account storage format
Accounts are stored in plugins/auth/accounts.json. Each account has:
| Field | Type | Description |
|---|---|---|
username | string | Lowercased canonical username |
display_name | string | Original-case username |
password_hash | string or null | Argon2id hash, or null for premium-only accounts |
registered_at | datetime | When the account was created |
last_login | datetime or null | Last successful login |
last_ip | string or null | Last known IP |
login_count | u64 | Total successful logins |
premium_info | object or null | Present if the player has been detected as premium |
The premium_info object:
| Field | Type | Description |
|---|---|---|
mojang_uuid | UUID | The player's official Mojang UUID |
force_cracked | bool | true if the player ran /cracked |
first_premium_login | datetime | When the player was first detected as premium |
last_premium_login | datetime or null | Last premium auto-login |
How premium detection works
The detection pipeline has three layers:
A
DashMapcache keyed by lowercase username, with a configurable TTL (cache_ttl_seconds). Cache hits avoid any network call.GET https://api.mojang.com/users/profiles/minecraft/<username>. A 200 means the username is premium. A 404 means it's not. A 429, or a lookup held back by the localrate_limit_per_secondgovernor, triggers therate_limit_actionpolicy. A network error, any other status or a malformed response triggerslookup_error_action.The API lookup alone is not trusted. It only determines whether to attempt the Mojang encryption handshake. The actual proof of identity is the RSA key exchange +
hasJoinedsession verification, handled by the proxy core.
Name squatting
If a cracked player registers "Steve" with a password, and then the real premium "Steve" connects:
- The Mojang API says "Steve" is premium.
- The proxy forces the encryption handshake. The real Steve's client proves ownership.
- The auth handler sees signed textures and accepts.
- The existing account is updated with
premium_info. The cracked player's password hash is preserved.
If the cracked "Steve" tries to connect later, the proxy forces encryption again. The cracked client fails the handshake and the plugin remembers the failure (see below). With the default premium_name_conflict_action = "kick", its next attempts are refused. With "allow_cracked", it can join as cracked on the next attempt and /login with its password.
If the premium "Steve" later changes their Mojang username, the cracked player can connect again and /login with their password as before. Premium identity is tracked by Mojang UUID, not by username.
Cracked players using premium usernames (second attempt)
A cracked player using a premium username (e.g. "Hypixel") will be kicked on their first connection. This is unavoidable: the proxy sends an encryption request, and the cracked client can't respond.
The plugin remembers the failure, and premium_name_conflict_action decides what the second connection gets:
"kick"(default): the connection is refused with thepremium_name_conflictmessage."allow_cracked": the plugin setsForceOffline. The player enters auth limbo and can/registeror/loginnormally.
This works in both proxy modes:
| Mode | First connection | Second connection, "kick" | Second connection, "allow_cracked" |
|---|---|---|---|
offline + premium | ForceOnline → encryption fails → kick | Refused with premium_name_conflict | ForceOffline (ignored, already offline) → auth limbo |
client_only | Default Mojang auth → encryption fails → kick | Refused with premium_name_conflict | ForceOffline → skip Mojang auth → auth limbo |
A name the Mojang API does not report as premium is no conflict. In client_only mode a cracked player using one fails the default Mojang auth once, and gets ForceOffline and auth limbo on the next attempt with either setting.
The remember window is controlled by failed_auth_remember_seconds (default: 10 minutes). After it expires, the proxy retries authentication once. If the player still can't complete it, they're remembered again. If they've since bought Minecraft, the auth succeeds and they get premium auto-login.
TIP
"allow_cracked" is the approach of FastLogin's secondAttemptCracked option. The first-connection kick is a protocol-level constraint that cannot be avoided.
Mojang session authentication
Mojang session auth is not part of this plugin. It's built into Infrarust's core and activates when you use client_only proxy mode.
In client_only mode, the proxy terminates the Minecraft connection and re-establishes it to the backend. During this process, Infrarust runs the standard Mojang authentication flow:
- The proxy generates a 1024-bit RSA key pair at startup.
- When a player connects, the proxy sends an
EncryptionRequestwith the public key and a random 4-byte verify token. - The client encrypts a shared secret and the verify token with the public key, then sends them back in an
EncryptionResponse. - The proxy decrypts both values using its private key and checks the verify token matches.
- The proxy computes a server hash (Minecraft's non-standard signed SHA-1 of the server ID, shared secret, and public key DER).
- The proxy calls
sessionserver.mojang.com/session/minecraft/hasJoined?username=<name>&serverId=<hash>to verify the player owns the account. - If Mojang confirms the session, the proxy enables AES/CFB8 encryption on the connection and returns the player's game profile (UUID, username, skin data).
Players using cracked or offline clients fail at step 6. With the auth plugin's premium detection enabled, cracked players get kicked on first connection, and the plugin remembers the failure. On their next attempt it sets ForceOffline to bypass encryption, unless the name is premium and premium_name_conflict_action is "kick": then the attempt is refused.
The premium auto-login feature reuses this same flow in both directions. In offline mode, the proxy can switch a connection to online mode via ForceOnline. In client_only mode, it can switch to offline via ForceOffline. Both are controlled by plugins through the PreLoginEvent.