MinecraftConsoles/docs/auth-system.md
MrTheShy 4f2352361a Add Mojang/Ely.by/offline authentication system
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.
2026-03-23 01:14:23 +01:00

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):

  1. Calls MCAuth::JoinServer(token, uuid, serverId) to sessionserver.mojang.com/session/minecraft/join
  2. On success, sends AuthResponsePacket with scheme "mojang"
  3. On failure, disconnects with eDisconnect_AuthFailed

Client side ("elyby" scheme):

  1. Calls MCAuth::ElybyJoinServer(token, uuid, serverId) to authserver.ely.by/session/minecraft/join
  2. On success, sends AuthResponsePacket with scheme "elyby"
  3. On failure, disconnects with eDisconnect_AuthFailed

Client side ("offline" scheme):

  1. 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:

  1. Server downloads the skin during verification (background thread)
  2. AuthResultPacket contains the raw PNG bytes
  3. Client registers the texture in memory via app.AddMemoryTextureFile()
  4. Server distributes the skin to all connected clients via TextureAndGeometryPacket
  5. 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_COUNT slots)
  • Each slot has its own JavaAuthManager and 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 ready with memory_order_acquire, then reads under mutex
  • cancelled flag lets PendingConnection destructor abort unnecessary work
  • shared_ptr prevents use-after-free if PendingConnection is destroyed before the thread finishes

MCAuthManager Slots

  • Each slot has its own mutex
  • Atomic generation counter 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