The old Windows64 port had no real player identity — it used hardcoded fake XUIDs, so anyone could impersonate anyone. This replaces that with proper auth supporting Mojang, Ely.by, and offline accounts. MCAuth library (new, MCAuth/): Mojang auth via MSA device code flow (XBL, SISU, MC services), Ely.by via Yggdrasil with 2FA, offline UUID generation matching Java Edition (MD5 v3 from "OfflinePlayer:<name>"). Multi-account manager with background token refresh, per-slot sessions, and on-disk token persistence. Server-side session verification via Mojang/Ely.by hasJoined API. Skin fetching and PNG validation from texture servers. Network protocol (version bumped to 80): Three new packets (AuthScheme, AuthResponse, AuthResult) implement a server-driven auth handshake before login completes. Player identity migrated from 64-bit XUID to 128-bit GameUUID backed by two uint64 fields (hi/lo). readPlayerUID/writePlayerUID now serialize 16 bytes on the wire. Old and new clients cannot connect to each other — version mismatch is rejected at PreLogin. Save migration: Map data mappings auto-migrate from old format: the old 64-bit XUID is placed in hi, lo is set to 0 as a sentinel. On first access by the real player, the sentinel entry is upgraded in-place to the full 128-bit UUID. Format detection is by file size (2080, 2112, or 4160 bytes). Player .dat filenames inside saveData.ms change from decimal XUID to dashed UUID — old saves need manual entry renaming in the archive. UI: NativeUIRenderer: immediate-mode drawing system (quads, text, 9-slice panels, scrollbars, focus lists) for rendering auth screens without Flash/Scaleform. UIScene_MSAuth handles device code display, Ely.by credential input with 2FA, per-account skin head preview, and multi-account add/remove/switch. Server: online-mode and auth-provider (mojang/elyby) in server.properties. Whitelist and ban checks validate against the server-verified UUID. Incompatible auth scheme logs which provider the server expects vs what the client is using. Also fixes a pre-existing exploit where any client could send a DebugOptionsPacket to grant themselves CraftAnything and other debug privileges on any server — now requires OP status server-side.
13 KiB
Authentication System - MinecraftConsoles
Overview
3-packet handshake between client and server, after PreLogin and before LoginPacket. Three schemes: Mojang (online verification via sessionserver.mojang.com), ely.by (online verification via authserver.ely.by), and offline (server-generated UUID from username).
Replaces the old system that relied solely on console platform XUIDs (Xbox Live, PSN) and had no identity verification for the Windows x64 build.
Packet Architecture
| Packet | ID | Direction | Description |
|---|---|---|---|
AuthSchemePacket |
170 | Server -> Client | Server offers auth schemes + challenge |
AuthResponsePacket |
171 | Client -> Server | Client picks a scheme and sends credentials |
AuthResultPacket |
172 | Server -> Client | Server confirms/rejects + assigns UUID and skin |
The LoginPacket serves as the handshake acknowledgement — no dedicated ACK packet needed.
Handshake Flow
CLIENT SERVER
| |
| ---- PreLoginPacket (netcode version) ----> |
| <--- PreLoginPacket (UGC, save info) ------ |
| |
| <--- AuthSchemePacket (170) --------------- | schemes=["mojang"|"elyby"|"offline"], serverId="a1b2c3..."
| |
| ---- AuthResponsePacket (171) ------------> | scheme="mojang", uuid, username
| |
| [Server verifies with Mojang on a |
| background thread + downloads skin] |
| |
| <--- AuthResultPacket (172) --------------- | success=true, assignedUuid, assignedUsername, skinData
| |
| ---- LoginPacket (1) ---------------------> | Completes auth handshake
| |
| <--- LoginPacket (1) --------------------- | Server responds with world data
| |
Packet Details
AuthSchemePacket (ID 170)
Direction: Server -> Client
Sent right after PreLogin response. Contains supported auth schemes and a random challenge.
class AuthSchemePacket : public Packet {
vector<wstring> schemes; // "mojang", "elyby", "offline"
wstring serverId; // random hex challenge (20 chars)
};
Wire format:
| Field | Type | Notes |
|---|---|---|
count |
int32 | Number of schemes (max 16) |
schemes[i] |
UTF string | Scheme name (max 32 chars) |
serverId |
UTF string | Hex challenge (max 64 chars) |
Read validation:
- Scheme count capped at 16
- Only
"mojang","elyby", and"offline"accepted; unknown strings silently discarded
Server behavior:
onlineMode=true: offers only["mojang"]onlineMode=true, authProvider="mojang": offers["mojang"]onlineMode=true, authProvider="elyby": offers["elyby"]onlineMode=false: offers["mojang", "offline"]
AuthResponsePacket (ID 171)
Direction: Client -> Server
Client picks a scheme and sends credentials.
class AuthResponsePacket : public Packet {
wstring chosenScheme; // "mojang", "elyby", or "offline"
wstring mojangUuid; // dashed 128-bit UUID
wstring username; // player username (max 16 chars)
};
Wire format:
| Field | Type | Notes |
|---|---|---|
chosenScheme |
UTF string | max 32 chars, validated against known values |
mojangUuid |
UTF string | max 64 chars |
username |
UTF string | max 16 chars |
Read validation:
- Scheme reset to
""if not"mojang","elyby", or"offline" - Username validated char by char: only
[a-zA-Z0-9_]. Invalid char clears the entire username
Client side ("mojang" scheme):
- Calls
MCAuth::JoinServer(token, uuid, serverId)tosessionserver.mojang.com/session/minecraft/join - On success, sends AuthResponsePacket with scheme
"mojang" - On failure, disconnects with
eDisconnect_AuthFailed
Client side ("elyby" scheme):
- Calls
MCAuth::ElybyJoinServer(token, uuid, serverId)toauthserver.ely.by/session/minecraft/join - On success, sends AuthResponsePacket with scheme
"elyby" - On failure, disconnects with
eDisconnect_AuthFailed
Client side ("offline" scheme):
- Sends AuthResponsePacket directly with UUID and username from the local session
AuthResultPacket (ID 172)
Direction: Server -> Client
Server communicates the verification result and assigns the player's final identity.
class AuthResultPacket : public Packet {
bool success;
wstring assignedUuid; // final UUID assigned by the server
wstring assignedUsername; // final username
wstring errorMessage; // empty if success=true
wstring skinKey; // memory-texture key (e.g. "mojang_skin_{uuid}.png")
vector<uint8_t> skinData; // raw PNG skin bytes
};
Wire format:
| Field | Type | Notes |
|---|---|---|
success |
bool | 1 byte |
assignedUuid |
UTF string | max 64 chars |
assignedUsername |
UTF string | max 64 chars |
errorMessage |
UTF string | max 256 chars |
skinKey |
UTF string | max 256 chars |
skinSize |
int32 | skin data length |
skinData |
byte[] | raw PNG (max 32KB) |
Skin validation:
- Max 32KB (a 64x64 RGBA skin PNG is ~4KB compressed)
- Excess bytes consumed to keep stream synchronized but not stored
- Client validates with
MCAuth::ValidateSkinPng()(PNG magic, 64x32 or 64x64 dimensions)
Server-Side Verification Flow
"mojang" Scheme (Online Mode)
PendingConnection::handleAuthResponse()
|
+--> m_authState = eAuth_Verifying
+--> Spawns detached std::thread:
|
+--> MCAuth::HasJoined(username, serverId)
| [GET sessionserver.mojang.com/session/minecraft/hasJoined]
|
+--> On success: MCAuth::FetchSkinPng(skinUrl)
| [Downloads skin PNG from Mojang CDN]
|
+--> Writes result to AuthVerifyResult (mutex-protected)
+--> ready.store(true)
PendingConnection::tick() [main thread]
|
+--> Checks m_authVerifyResult->ready
+--> On success:
| +--> Sends AuthResultPacket(success=true, uuid, username, skinKey, skinData)
| +--> m_authState = eAuth_WaitingAck
|
+--> On failure:
+--> Sends AuthResultPacket(success=false, errorMessage)
+--> Disconnects client
"offline" Scheme
PendingConnection::handleAuthResponse()
|
+--> Checks that server->onlineMode == false
+--> Generates offline UUID: GameUUID::generateOffline(username)
| [UUID v3, namespace "OfflinePlayer:" + username]
+--> Sends AuthResultPacket(success=true, offlineUuid, username)
+--> m_authState = eAuth_WaitingAck
In offline mode, the server generates the UUID itself from the username. The client-sent UUID is ignored, preventing UUID forgery.
State Machine (Server - PendingConnection)
eAuth_None
|
+--> sendPreLoginResponse() sends AuthSchemePacket
|
v
eAuth_WaitingResponse
|
+--> handleAuthResponse() received
|
v
eAuth_Verifying (only for "mojang" scheme)
|
+--> tick() detects result ready
|
v
eAuth_WaitingAck
|
+--> handleLogin() received
|
v
eAuth_Done
|
+--> handleLogin() proceeds normally
If a LoginPacket arrives in state eAuth_None, eAuth_WaitingResponse, or eAuth_Verifying, the connection is closed with eDisconnect_AuthFailed.
Channel Security
The game protocol is plaintext — no AES, no TLS on the game socket. SharedKeyPacket (vanilla Java's encryption setup) is disabled. This is fine because no secrets travel over the game channel.
What travels on the game socket (plaintext):
- UUID, username, skin PNG, serverId challenge, gamestate
What never travels on the game socket:
- Access tokens, refresh tokens, session tokens
The Mojang access token stays local on the client. It's sent only to sessionserver.mojang.com via HTTPS (WINHTTP_FLAG_SECURE in MCAuthHttp.cpp). The game server never sees it — it verifies the player by calling HasJoined on Mojang's side, also over HTTPS.
The serverId challenge is random per connection (20 hex chars, generated in PendingConnection). A captured serverId is useless — Mojang consumes the (username, serverId) binding on the first JoinServer call. Replay is not possible.
On console platforms (Xbox/QNet, PS/SQR), the transport layer provides its own integrity. On Windows64 it's raw TCP — an attacker on the same network can see gamestate (positions, chat, inventory), but that's a cheat/sniffing concern, not an auth one. Same situation as vanilla Java pre-1.7.
Mojang Skin Handling
The server downloads skins and distributes them inline:
- Server downloads the skin during verification (background thread)
- AuthResultPacket contains the raw PNG bytes
- Client registers the texture in memory via
app.AddMemoryTextureFile() - Server distributes the skin to all connected clients via
TextureAndGeometryPacket - New clients receive existing skins at join time
MCAuth Library
The authentication backend lives in MCAuth/.
JavaAuthManager
- Login via Device Code Flow (OAuth 2.0) — user visits
microsoft.com/link - Automatic token refresh
- Token save/load to disk (JSON)
Verification Functions
| Function | Usage |
|---|---|
JoinServer() |
Client: POST to sessionserver to announce join |
HasJoined() |
Server: GET to sessionserver to verify identity |
FetchSkinPng() |
Downloads skin PNG from CDN |
FetchProfileSkinUrl() |
Gets skin URL from Mojang profile |
ValidateSkinPng() |
Validates PNG (magic bytes + 64x32 or 64x64 dimensions) |
UUID Utilities
| Function | Usage |
|---|---|
GenerateOfflineUuid() |
UUID v3 from username (vanilla-compatible) |
DashUuid() / UndashUuid() |
UUID format conversion |
MakeSkinKey() |
Generates memory texture key (e.g. "mojang_skin_069a79f4.png") |
MCAuthManager (Singleton)
- Multi-account with splitscreen support (up to
XUSER_MAX_COUNTslots) - Each slot has its own
JavaAuthManagerand independent session - Background workers: detached threads capturing
shared_ptr, invalidated by generation counter - Cancellation via
RequestCancel() - Account persistence in
java_accounts.json
Thread Safety
AuthVerifyResult (PendingConnection)
struct AuthVerifyResult {
std::atomic<bool> ready{false};
std::atomic<bool> cancelled{false};
std::mutex mutex;
bool success;
std::string username, uuid, skinUrl;
std::vector<uint8_t> skinData;
};
- Background thread writes under mutex, then
ready.store(true, memory_order_release) - Main thread checks
readywithmemory_order_acquire, then reads under mutex cancelledflag lets PendingConnection destructor abort unnecessary workshared_ptrprevents use-after-free if PendingConnection is destroyed before the thread finishes
MCAuthManager Slots
- Each slot has its own mutex
- Atomic
generationcounter invalidates stale workers - Workers capture
shared_ptr<JavaAuthManager>by value — if the slot is reset, the old engine stays alive until the thread exits
Files
Packets (Minecraft.World/)
| File | Description |
|---|---|
AuthSchemePacket.h/.cpp |
Scheme offer packet |
AuthResponsePacket.h/.cpp |
Client response packet |
AuthResultPacket.h/.cpp |
Result packet + skin data |
DisconnectPacket.h |
eDisconnect_AuthFailed = 31 |
PacketListener.h |
Auth packet handlers |
GameUUID.h/.cpp |
UUID utilities (fromDashed, generateOffline) |
Server (Minecraft.Client/)
| File | Description |
|---|---|
PendingConnection.h/.cpp |
Auth state machine, Mojang verification, skin distribution |
PlayerList.cpp |
Ban check against auth UUID |
Client (Minecraft.Client/)
| File | Description |
|---|---|
ClientConnection.h/.cpp |
handleAuthScheme, handleAuthResult, sendLoginPacketAfterAuth |
UIScene_MSAuth.h/.cpp |
Account management UI (list, device code, offline) |
Auth Library (MCAuth/)
| File | Description |
|---|---|
MCAuth.h |
Public API: login, verification, skin, UUID |
MCAuthManager.h |
Multi-account singleton with splitscreen support |
MCAuth.cpp |
UUID utilities, skin validation |
MCAuthJava.cpp |
Java Edition auth flow (MSA -> XBL -> Mojang) |
MCAuthElyby.cpp |
Ely.by Yggdrasil auth (login, refresh, validate, token persistence) |
MCAuthSession.cpp |
Session verification (JoinServer, HasJoined, Elyby variants) |
MCAuthHttp.cpp/.h |
HTTP client (WinHTTP) |
MCAuthCrypto.cpp/.h |
Base64, SHA, JSON parsing |
MCAuthPlatform.cpp |
File paths, persistence |