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

360 lines
13 KiB
Markdown

# 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.
```cpp
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.
```cpp
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.
```cpp
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)
```cpp
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 |