Complete Velocity authentication plugin with intelligent nickname protection, premium auto-login, and secure offline player management.
VeloAuth is a comprehensive authentication system for Velocity proxy that handles all player authorization before they reach your backend servers. It works with any limbo server to provide a smooth login experience while protecting nickname ownership through intelligent conflict resolution.
- 🔒 Intelligent Nickname Protection - Premium nicknames are reserved unless already registered by cracked players
- ⚡ Premium Auto-Login - Mojang account owners skip authentication automatically
- 🔄 Automatic Nickname Change Detection - Detects when a premium player renames their Mojang account and updates the database record automatically
- 🛡️ Secure Offline Auth - BCrypt password hashing with brute-force protection
- 📱 Optional Floodgate Support - Bedrock players can bypass the auth server when Floodgate integration is enabled
- 🗺️ Forced Hosts Support - Players connect via custom domains (e.g.,
pvp.server.com) and are properly routed to their intended server after authentication - 🚫 Smart Command Hiding - Authentication commands (
/login,/register) are completely hidden from tab-completion once the player is logged in - 🚀 High Performance - Three-layer premium resolution cache: in-memory → database → external API, with 24-hour premium status retention
- 🔐 Optional 2FA (TOTP) - Opt-in RFC 6238 second factor compatible with Google Authenticator, Authy, Aegis. See 2FA.md for the operator + player handbook.
- 🔄 Conflict Resolution - Smart handling of premium/cracked nickname conflicts
- 📊 Admin Tools - Complete conflict management with
/vauth conflicts - 🗄️ Multi-Database - MySQL, PostgreSQL, H2, SQLite
- 🌍 17 Languages - EN, PL, DE, FR, RU, TR, SI, FI, ZH_CN, ZH_HK, JA, HI, VI, KO, TH, ID, PT_BR
- 🔄 LimboAuth Compatible - 100% database compatibility (no migration needed)
- 📢 Discord Alerts - Webhook notifications for security events
- 🧵 Virtual Threads - Built on Java 21 for maximum performance
- 📈 bStats Analytics - Anonymous usage statistics via bStats
- You run a Velocity proxy with one or more backend servers and need authentication at the proxy layer (not per-backend).
- You accept both premium and cracked players and need automatic, fail-secure routing — premium players skip
/login, cracked players go through BCrypt-hashed registration. - You already use LimboAuth and want to migrate without losing data — VeloAuth reads the same database schema.
- You want predictable performance — premium status is resolved through a three-layer cache (in-memory → DB → Mojang/Ashcon API), virtual-thread I/O, and zero blocking on Velocity event threads.
If you only run a single backend server (Paper/Spigot/Folia) without a proxy, you don't need VeloAuth — use a backend-side auth plugin instead.
See CHANGELOG.md for release-by-release notes, upgrade instructions, and breaking-behavior callouts.
- Java 21 or newer
- Velocity proxy (API 3.4.0+)
- Limbo server: NanoLimbo, LOOHP/Limbo, LimboService, PicoLimbo, hpfxd/Limbo, or any other
- Database: MySQL, PostgreSQL, H2, or SQLite
- Download VeloAuth from Modrinth
- Place the file in your Velocity
plugins/folder - Start Velocity - the plugin will create a
config.ymlfile - Stop Velocity and configure your database and auth server name in
plugins/VeloAuth/config.yml - Restart Velocity
Note: Floodgate support is disabled by default. Enable it only if you actually use Geyser/Floodgate.
Configure your velocity.toml with your limbo/auth server and backend servers:
[servers]
limbo = "127.0.0.1:25566" # Auth/limbo server (NanoLimbo, LOOHP/Limbo, etc.)
lobby = "127.0.0.1:25565" # Typical backend server
survival = "127.0.0.1:25567" # Another backend server
try = ["lobby", "survival"] # Order matters. Do NOT put 'limbo' here.
[forced-hosts]
# VeloAuth fully respects Velocity's forced hosts!
# Players connecting via this IP will be sent to limbo to login,
# and then seamlessly transferred to 'survival' instead of 'lobby'.
"survival.example.com" = ["survival"] Important: The try configuration controls where authenticated players are redirected by default. VeloAuth automatically skips the limbo server and selects the first available backend server, unless the player used a forced-host domain, in which case they are natively routed to their intended destination!
Minimal auth server configuration in plugins/VeloAuth/config.yml:
language: en
# Built-in language codes: "en", "pl", "si", "ru", "tr", "fr", "de", "fi", "zh_cn", "zh_hk", "ja", "hi", "vi", "ko", "th", "id", "pt_br"
auth-server:
server-name: limbo
# Seconds before an unauthenticated player is kicked from the auth server.
# Set to 0 to disable the kick (player can stay on auth/limbo indefinitely).
timeout-seconds: 300Default policy is length-only (8–72 chars) — friendly for casual servers, backward-compatible with LimboAuth. If a player sets a weak password under default rules, that's on them. Enable stricter rules only when you actually need them.
security:
min-password-length: 8
max-password-length: 72
# All counters default to 0 = no extra constraint.
# Set any to >0 to require that many characters of the given class.
password-policy:
min-digits: 0 # 0 = off, e.g. 1 = require at least one digit
min-uppercase: 0 # 0 = off, e.g. 1 = require at least one uppercase letter
min-lowercase: 0 # 0 = off, e.g. 1 = require at least one lowercase letter
min-special: 0 # 0 = off, e.g. 1 = require at least one special character
# (special = anything that is NOT a letter or digit)Profiles you can copy in:
| Profile | digits | upper | lower | special | When to use |
|---|---|---|---|---|---|
| Relaxed (default) | 0 | 0 | 0 | 0 | Casual SMP, friendly servers, lobby networks |
| Standard | 1 | 1 | 1 | 0 | Mid-sized servers with staff accounts |
| Strict | 1 | 1 | 1 | 1 | Servers with economy/premium tiers or regulated regions |
Counters apply on top of min-password-length. Validation error messages (validation.password.needs_digit/upper/lower/special) are localized to all 17 supported languages.
If your setup runs DiscordSRV or another plugin that kicks players before VeloAuth's backend-wait flow finishes, you can silence the in-chat "Waiting for a server…" notifications without forking the plugin: open the language file under plugins/VeloAuth/lang/messages_<lang>.properties and set the keys to empty values:
connection.waiting_for_server=
connection.error.no_servers=Empty value = sendMessage is suppressed; logs are still written. Backend transfer retries continue regardless.
Built-in language codes you can copy directly into config:
| Code | Language |
|---|---|
en |
English |
pl |
Polish |
si |
Slovenian |
ru |
Russian |
tr |
Turkish |
fr |
French |
de |
German |
fi |
Finnish |
zh_cn |
Chinese Simplified |
zh_hk |
Chinese Traditional (Hong Kong) |
ja |
Japanese |
hi |
Hindi |
vi |
Vietnamese |
ko |
Korean |
th |
Thai |
id |
Indonesian |
pt_br |
Brazilian Portuguese |
Optional Floodgate integration:
floodgate:
enabled: true
username-prefix: "."
bypass-auth-server: trueKeep the Floodgate prefix aligned with your proxy-side Floodgate configuration.
VeloAuth posts Discord webhook alerts when premium-resolver failure rates breach a threshold. Wired through PremiumResolverAlertService and triggered on every Mojang/Ashcon resolver attempt.
alerts:
enabled: true # master switch; false disables all alerting
discord:
enabled: true
webhook-url: "https://discord.com/api/webhooks/.../..."
failure-rate-threshold: 0.5 # alert when ≥50% of resolutions fail
min-requests-for-alert: 10 # don't alert until at least N attempts in the window
check-interval-minutes: 5 # rolling metric window
alert-cooldown-minutes: 30 # minimum gap between two alertsSupported: H2 (out-of-box), MySQL, PostgreSQL, SQLite
| Command | Description | Restrictions |
|---|---|---|
/register <password> <confirm> |
Create new account | Hidden after login. No premium nicknames |
/login <password> |
Login to your account | Hidden after login. Works for all players |
/changepassword <old> <new> |
Change your password | Must be logged in |
/2fa setup |
Enroll a TOTP authenticator (see 2FA.md) | Must be logged in. Disabled when two-factor.enabled: false |
/2fa verify <code> |
Confirm enrollment OR pass 2FA at login | — |
/2fa disable <code> |
Disable 2FA on your account | Requires a valid code |
/2fa qr / /2fa status |
Re-show QR / show 2FA status | Must be logged in |
| Command | Permission | Description |
|---|---|---|
/unregister <nickname> |
veloauth.admin |
Remove player account (resolves conflicts) |
/vauth reload |
veloauth.admin |
Reload configuration |
/vauth cache-reset [player] |
veloauth.admin |
Clear authorization cache |
/vauth stats |
veloauth.admin |
Show plugin statistics |
/vauth conflicts |
veloauth.admin |
List nickname conflicts |
/vauth 2fa-remove <nickname> |
veloauth.admin |
Recovery: wipe a player's 2FA token (see 2FA.md) |
- Player connects to Velocity
- VeloAuth checks in-memory authorization cache (instant, no I/O)
- If not in memory, checks database premium cache (persistent across restarts)
- If not in DB cache, resolves via Mojang/Ashcon API in parallel using virtual threads
- If not premium, player is sent to the auth server (unless Floodgate Bedrock bypass applies)
- Player types /login or /register
- VeloAuth verifies credentials with BCrypt
- Player is redirected to backend server via
tryconfiguration
Connect → [In-memory cache] → [Database cache] → [Mojang/Ashcon API]
~0ms ~1ms ~200-500ms
All API calls run in parallel on virtual threads. Results are cached in the database and survive proxy restarts.
When a premium player logs in with a different username than what is stored (Mojang account rename), VeloAuth automatically detects the mismatch and updates the database record, keeping the UUID-to-username mapping accurate without any admin intervention.
- Premium nicknames are reserved unless already registered by cracked players
- Conflict resolution when premium players use cracked-registered nicknames
- Admin tools for managing nickname conflicts
- Automatic blocking of cracked players trying premium nicknames
Q: A cracked player tries to join with a premium nickname and gets "You are not logged into your Minecraft account."
This is VeloAuth actively enforcing nickname-theft protection, not Velocity's own online-mode check. When premium detection finds the nickname in Mojang's database and no record exists in VeloAuth's DB yet, VeloAuth calls Velocity's PreLoginComponentResult.forceOnlineMode() — which forces Mojang session-server auth regardless of online-mode = false in velocity.toml. A cracked client cannot pass that handshake and gets kicked.
If your server explicitly accepts cracked players on premium nicknames, you have three options:
- Recommended — opt in per-nickname behavior: set
premium.allow-cracked-on-premium-nicks: trueinplugins/VeloAuth/config.yml. Premium nicks with no DB record will be forced into offline mode so a cracked client can register first. Premium owners returning to a nickname that's already registered as premium still get the normal Mojang handshake. - Disable premium detection entirely: set
premium.check-enabled: false. Removes nickname-theft protection for all nicks — every connection is forced offline. - Pre-register the nickname: have an admin (or the cracked player) register the nickname through
/registerbefore the premium owner tries to join. The existing nickname-conflict path then routes that nickname to offline mode automatically.
Important trade-off for options 1 and 2: once a premium nickname is registered as offline in VeloAuth, the real Mojang owner can no longer take it back automatically — they will hit the nickname-conflict flow.
Q: The Failed to transfer player X: TextComponentImpl{content="...", style=StyleImpl{...}} spam in logs is gone — anything I need to do?
No action needed. VeloAuth 1.2.0+ renders kick reasons as plain text via KickReasonRenderer. Log lines now read e.g. Failed to transfer player Alice to server lobby (Status: CONNECTION_CANCELLED): You must link your Discord account to play.
Q: My language: en config still shows Polish strings in some logs.
Fixed in 1.2.0 — all operator-facing log messages and exception strings are now English regardless of language setting. The language key only controls player-facing messages.
Q: Database not connected" right after Velocity startup.
Fixed in 1.2.0 — health check runs once synchronously before the 30s scheduler kicks in. The isConnected() gate used by admin commands now reflects pool state (initialized + not shut down) rather than waiting for the first health check.
VeloAuth is 100% compatible with LimboAuth databases:
- Stop LimboAuth on your backend servers
- Install VeloAuth on Velocity
- Configure VeloAuth to use the same database as LimboAuth
- Start Velocity - all existing accounts will work automatically
Contributions are welcome! Please open an issue or PR.
Need help? Found a bug? Open an issue on GitHub or join our Discord server.
This project is licensed under the MIT License - see the LICENSE file for details.
