Authentication Flows
This page walks through every flow in AuthCore, step by step, and names the functions involved at each point. It is written for server owners who want to understand what the mod does and why, without reading the source. Each flow ends with a short "why it exists" note.
How to read this page
Class.methodnames point at the actual code so you can jump in and look around.- "Lobby" and "limbo" are the same thing: the restricted waiting area every unauthenticated player is held in.
- "Premium" means a paid (Mojang) Minecraft account. "Standard" means a cracked/offline account.
1. The join flow
Goal: decide, for every incoming player, whether they are premium or standard, and then either let them in or lock them into the limbo.
- The login packet arrives.
ServerLoginNetworkHandlerMixin.handleHelloreads the player name and UUID from the hello packet (the shape differs between Minecraft versions, so it is read reflectively). - Server mode is detected.
AuthCoreServer.detectServerOnlineMode+Compat.serverUsesAuthenticationread the realserver.properties online-modesetting. This decides whether the server verifies Mojang sessions at all (see flow 7). - Optional premium verification starts.
On offline-mode servers with premium auto-login enabled,
ServerPremiumVerificationMixinstarts the vanilla encryption handshake so the client hands over its session token (flow 7 explains the details). - The play connection becomes ready.
ServerEvents.onPlayerJoinis the main gate. In order it checks:- duplicate login (
user.isActive,blockDuplicateRegister,blockDuplicateSession), - maintenance mode, join rate limits (
RateLimiter), IP rules (IpRules.isDenied), proxy/VPN blocking (McApiManager.geoIp+user.isProxy), - the same-IP session rule and the persistent account lock (
user.isLocked), - then
user.connect(connection)wires the connection to the account.
- duplicate login (
- Three ways to get in:
- Session resume (same IP + still-valid session) calls
user.login(player)directly (flow 5). - Premium auto-login (verified premium, or a brand-new account on a server that
verified the session) calls
user.login(player)directly - auto-login players are never given a generated password (their stored password stays null). A player who explicitly switched their account to password login (/account set-mode offline) is not auto-logged-in: they are asked to/registeror/loginlike any offline account. - Everyone else is locked into the limbo with
user.lobby.lock()(flow 2) and asked to/registeror/login.
- Session resume (same IP + still-valid session) calls
Why it exists: without this gate, anyone could walk onto the server, claim a name, and play or grief before proving who they are. The join flow is the single place where every account is classified and every security rule is applied.
2. The limbo (lockdown) flow
Goal: hold unauthenticated players in a tiny, fully restricted bubble until they prove their identity.
- Locking:
Lobby.lock()takes aLobby.Snapshot(inventory, effects, health, food, XP, position, game mode, operator status) so everything can be restored later. ThenLobby.handleTeleport()picks the anchor position: the configured limbo spawn for new players, or the player's own position for returning players whenlimbo-config.only-on-first-timeis on. Blindness, invisibility and adventure mode are applied, and the player is dismounted from any vehicle. - Enforcement: every restriction is enforced at the packet or game-mode level by a mixin:
- movement:
ServerPlayNetworkHandlerMixincancels move packets and snaps the client back withLobby.teleportBack();EntityMixinstops server-side movement; the per-tick guard inServerEvents.onEndServerTick(Lobby.isFarFromLobbyPos) re-teleports any drift, - chat:
ServerPlayNetworkHandlerChatMixin, - commands: the
CommandManager*mixins enforce the whitelist (login/register/account stay allowed), - blocks and items:
ServerPlayerInteractionManagerMixinblocks breaking, placing and item use;ServerPlayerMixinblocks item drops;InventoryMixin,ItemEntityMixinand theScreenHandler*mixins block inventory/pickup tricks, - attacks and interactions:
EntityEventsplusMobEntityMixin(mobs cannot target you),ServerPlayerRestrictionsMixinblocks riding and sleeping, - damage:
EntityEvents.onEntityDamagemakes the player invulnerable in the lobby.
- movement:
- Reminders:
Lobby.handleTimeoutschedules periodic "use /register or /login" action-bar messages and the login-timeout kick. - Unlocking: after a successful login,
Lobby.unlock()callsSnapshot.reset(player), which restores everything: inventory, effects, health, position, game mode, operator status, the previous vehicle, and lands airborne players safely (no fall damage, no suffocation).
Why it exists: the limbo is the core of a secure server: attackers, bots and name-squatters have nothing to interact with until they authenticate, and legitimate players get back exactly what they had when they leave.
3. The register flow
Goal: let a brand-new player create an account with a password.
/register <password> <confirm>runsRegister.registerCommand. It re-verifies the prerequisites (the player must be in the lobby and unregistered), applies the command cooldown, and checks the account lock.- The password is validated against the policy with
Security.Password.check(upper/lower/ digit counts and total length frompassword-rules). User.register(player, password)hashes the password withEncrypter.hash(argon2 by default, self-contained salt), writes the account to the database (User.db.insert), and either logs the player straight in (allowLoginAfterRegistration) or keeps them in the lobby.- If hashing ever fails, the player is locked into the lobby instead of being left in a broken half-registered state.
Why it exists: registration gives each player a secret only they know, so later logins can prove it is really them.
4. The login flow
Goal: let a registered player prove their password and leave the limbo.
/login <password>runsLogin.execute: cooldown, then it checks the attempt counter and the account lock. After a successful login the human-verification observation window starts (only bot-like players are ever challenged).- The stored hash is checked with
Encrypter.verify. The verifier tries the stored algorithm first and falls back through the others, so old or imported hashes still verify without console errors. - If the stored hash uses a weak or outdated algorithm,
Login.upgradeHashIfNeededtransparently re-hashes it with the configured algorithm (you never notice). - If 2FA is enabled, a TOTP or email code is required (
Security.TOTPManager,Security.EmailOtp). User.login(player)records the login, issues a fresh session token, broadcasts the auth state (AuthInterop.broadcast) so proxies and other mods know, imports any DiscordSRV link, and unlocks the lobby, which restores the player (flow 2, step 4).
Why it exists: login is the proof step: the password plus any second factor is how the server knows the player behind the name is the account owner.
5. The session resume flow
Goal: players who logged in recently should not type their password every time they rejoin.
- During the join flow,
ServerEvents.onPlayerJoinchecks: same UUID, same IP (session-from-same-ip-only) and an active session (user.isActiveSession, withinsession.timeout-ms). - If all match,
user.login(player)runs directly and the player skips the lobby. - Clients that echo a valid session token over the interop channel resume with full trust
(
ClientGuard.verifySessionClaim); plain vanilla clients resume by IP with a small risk penalty whenrequire-token-for-resumeis enabled.
Why it exists: sessions are the convenience layer. The IP lock keeps the convenience from becoming a hijack vector: a stolen IP alone is not enough, and tokens rotate on every login.
6. The logout flow
Goal: end a session cleanly.
/account logoutcallsUser.logout(payload): the session is invalidated, the Redis session is removed, the interop channel is told the player is no longer authenticated (AuthInterop.broadcast(player, false)), and the player is disconnected.- When a player simply leaves,
ServerEvents.onPlayerLeavedoes the same cleanup: lobby unlock, in-memory caches dropped (ClientGuard, DiscordSRV), combat-log punishment applied if configured.
Why it exists: logout protects the account on shared computers and keeps every other system (proxies, Discord bridges, the web panel) in sync with reality.
7. The premium verification flow
Goal: let genuine premium (paid) accounts auto-login on online and offline-mode servers without AuthCore ever calling the Mojang web API.
- Online-mode servers: vanilla itself verifies every profile with Mojang
(
hasJoinedServer). The login mixin captures the verified profile and callsAuthCoreServer.markPremiumVerified. - Offline-mode servers (premium auto-login enabled):
ServerPremiumVerificationMixinforces the vanilla encryption handshake during login (Compat.loginStartHandshake). The client answers with its session token.Compat.loginFinishHandshakedecrypts the token and activates connection encryption.Compat.loginVerifyProfilecalls the server's OWNMinecraftSessionService.hasJoinedServer- exactly what vanilla does in online mode, no external HTTP from AuthCore.- Success marks the account premium; the join flow then auto-logs-in with a null password (no generated password is ever stored for auto-login accounts).
- Failure (cracked client, fake session, Mojang outage) simply continues the join as a standard player: lobby + register/login. Nobody is ever kicked.
- If the client never answers the handshake, a 15 second watchdog replays the hello and continues offline.
- Own-mode choice wins: an account the player or an admin explicitly set to password
login (
/account set-mode offline//authcore set-mode offline <player>) is never auto-logged-in, even when the server verified its Mojang session - it is asked to register or login. Switching back is just/account set-mode online. - Stale flags: accounts flagged premium in the database from older builds are downgraded on standard-auth servers (flow 8).
Why it exists: online-mode players are trusted (Mojang already verified them), so they skip the password step. Doing it through the server's own session service keeps it unforgeable and removes the fragile HTTP name-lookups entirely.
8. The auto-migration flow
Goal: upgrade old configs, databases and messages without manual work.
- Config:
ConfigMigrator.migrate()runs after config load, applies registered version steps, bumpsconfig.versionand saves. New keys are added automatically by Configurate. - Messages: enriched multi-channel defaults are copied only for templates that still hold the old single-channel values (custom messages are preserved).
- Database schema:
Database.load()adds missing columns automatically (and suspends auth with clear instructions if that is impossible). - Account flags: on the first detection of a standard-auth server,
Database.downgradePremiumAccounts()clears stale premium flags once per boot. - Password hashes: weak legacy hashes are upgraded on the next successful login (flow 4).
Why it exists: upgrades should never ask the owner to rebuild databases or hunt for new settings. Every migration is fail-safe: if it cannot run, the mod keeps working and tells you what to fix.
Failure safety guarantees
- No code path can crash the server: every mixin uses exact descriptors with
require = 0, so version-specific signatures are skipped, never fatal. - Every external call (GeoIP, session verification) is cached, timeout-limited and best-effort; failures downgrade the experience, never the server.
- Unverified players are always held in the limbo, never silently admitted and never accidentally kicked.
๐ Scale & concurrency guarantees
- 500k+ accounts, thousands of concurrent players: every hot path is a constant-time
ConcurrentHashMaplookup (User.getUser), the database is only touched on cache miss, and bounded structures everywhere keep memory flat at any player count. - No resource spikes: per-user throttles (touches, teleports, reminders), cached external lookups, rate limits and a fixed-size daemon I/O pool absorb join/login bursts without a CPU, memory or thread cliff.
- Race-condition-free: canonical single-instance-per-account cache under one lock,
synchronizeddatabase access (single shared connection),volatileshared config/messages, deduped join/leave hooks and atomic counters - concurrent joins, logins, web-panel actions, Redis events and backups never interleave or deadlock.