16 KiB
Complete Authentication Guide - Auth Server to World Server
Overview
This guide demonstrates the complete authentication flow in wowee, from connecting to the auth server through world server authentication. This represents the complete implementation of WoW 3.3.5a client authentication.
Complete Authentication Flow
┌─────────────────────────────────────────────┐
│ 1. AUTH SERVER AUTHENTICATION │
│ ✅ Connect to auth server (3724) │
│ ✅ LOGON_CHALLENGE / LOGON_PROOF │
│ ✅ SRP6a cryptography │
│ ✅ Get 40-byte session key │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 2. REALM LIST RETRIEVAL │
│ ✅ REALM_LIST request │
│ ✅ Parse realm data │
│ ✅ Select realm │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 3. WORLD SERVER CONNECTION │
│ ✅ Connect to world server (realm port) │
│ ✅ SMSG_AUTH_CHALLENGE │
│ ✅ CMSG_AUTH_SESSION │
│ ✅ Initialize RC4 encryption │
│ ✅ SMSG_AUTH_RESPONSE │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 4. READY FOR CHARACTER OPERATIONS │
│ 🎯 CMSG_CHAR_ENUM (next step) │
│ 🎯 Character selection │
│ 🎯 CMSG_PLAYER_LOGIN │
└─────────────────────────────────────────────┘
Complete Code Example
#include "auth/auth_handler.hpp"
#include "game/game_handler.hpp"
#include "core/logger.hpp"
#include <iostream>
#include <thread>
#include <chrono>
using namespace wowee;
int main() {
// Enable debug logging
core::Logger::getInstance().setLogLevel(core::LogLevel::DEBUG);
// ========================================
// PHASE 1: AUTH SERVER AUTHENTICATION
// ========================================
std::cout << "\n=== PHASE 1: AUTH SERVER AUTHENTICATION ===" << std::endl;
auth::AuthHandler authHandler;
// Stored data for world server
std::vector<uint8_t> sessionKey;
std::string accountName = "MYACCOUNT";
std::string selectedRealmAddress;
uint16_t selectedRealmPort;
// Connect to auth server
if (!authHandler.connect("logon.myserver.com", 3724)) {
std::cerr << "Failed to connect to auth server" << std::endl;
return 1;
}
// Set up auth success callback
bool authSuccess = false;
authHandler.setOnSuccess([&](const std::vector<uint8_t>& key) {
std::cout << "\n[SUCCESS] Authenticated with auth server!" << std::endl;
std::cout << "Session key: " << key.size() << " bytes" << std::endl;
// Store session key for world server
sessionKey = key;
authSuccess = true;
// Request realm list
std::cout << "\nRequesting realm list..." << std::endl;
authHandler.requestRealmList();
});
// Set up realm list callback
bool gotRealms = false;
authHandler.setOnRealmList([&](const std::vector<auth::Realm>& realms) {
std::cout << "\n[SUCCESS] Received realm list!" << std::endl;
std::cout << "Available realms: " << realms.size() << std::endl;
// Display realms
for (size_t i = 0; i < realms.size(); ++i) {
const auto& realm = realms[i];
std::cout << "\n[" << (i + 1) << "] " << realm.name << std::endl;
std::cout << " Address: " << realm.address << std::endl;
std::cout << " Population: " << realm.population << std::endl;
std::cout << " Characters: " << (int)realm.characters << std::endl;
}
// Select first realm
if (!realms.empty()) {
const auto& realm = realms[0];
std::cout << "\n[SELECTED] " << realm.name << std::endl;
// Parse realm address (format: "host:port")
size_t colonPos = realm.address.find(':');
if (colonPos != std::string::npos) {
std::string host = realm.address.substr(0, colonPos);
uint16_t port = std::stoi(realm.address.substr(colonPos + 1));
selectedRealmAddress = host;
selectedRealmPort = port;
gotRealms = true;
} else {
std::cerr << "Invalid realm address format" << std::endl;
}
}
});
// Set up failure callback
authHandler.setOnFailure([](const std::string& reason) {
std::cerr << "\n[FAILED] Authentication failed: " << reason << std::endl;
});
// Start authentication
std::cout << "Authenticating as: " << accountName << std::endl;
authHandler.authenticate(accountName, "mypassword");
// Wait for auth and realm list
while (!gotRealms &&
authHandler.getState() != auth::AuthState::FAILED) {
authHandler.update(0.016f);
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
// Check if authentication succeeded
if (!authSuccess || sessionKey.empty()) {
std::cerr << "Authentication failed" << std::endl;
return 1;
}
if (!gotRealms) {
std::cerr << "Failed to get realm list" << std::endl;
return 1;
}
// ========================================
// PHASE 2: WORLD SERVER CONNECTION
// ========================================
std::cout << "\n=== PHASE 2: WORLD SERVER CONNECTION ===" << std::endl;
std::cout << "Connecting to: " << selectedRealmAddress << ":"
<< selectedRealmPort << std::endl;
game::GameHandler gameHandler;
// Set up world connection callbacks
bool worldSuccess = false;
gameHandler.setOnSuccess([&worldSuccess]() {
std::cout << "\n[SUCCESS] Connected to world server!" << std::endl;
std::cout << "Ready for character operations" << std::endl;
worldSuccess = true;
});
gameHandler.setOnFailure([](const std::string& reason) {
std::cerr << "\n[FAILED] World connection failed: " << reason << std::endl;
});
// Connect to world server with session key from auth server
if (!gameHandler.connect(
selectedRealmAddress,
selectedRealmPort,
sessionKey, // 40-byte session key from auth server
accountName, // Same account name
12340 // WoW 3.3.5a build
)) {
std::cerr << "Failed to initiate world server connection" << std::endl;
return 1;
}
// Wait for world authentication to complete
while (!worldSuccess &&
gameHandler.getState() != game::WorldState::FAILED) {
gameHandler.update(0.016f);
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
// Check result
if (!worldSuccess) {
std::cerr << "World server connection failed" << std::endl;
return 1;
}
// ========================================
// PHASE 3: READY FOR GAME
// ========================================
std::cout << "\n=== PHASE 3: READY FOR CHARACTER OPERATIONS ===" << std::endl;
std::cout << "✅ Auth server: Authenticated" << std::endl;
std::cout << "✅ Realm list: Received" << std::endl;
std::cout << "✅ World server: Connected" << std::endl;
std::cout << "✅ Encryption: Initialized" << std::endl;
std::cout << "\n🎮 Ready to request character list!" << std::endl;
// TODO: Next steps:
// - Send CMSG_CHAR_ENUM
// - Receive SMSG_CHAR_ENUM
// - Display characters
// - Send CMSG_PLAYER_LOGIN
// - Enter world!
// Keep connection alive
std::cout << "\nPress Ctrl+C to exit..." << std::endl;
while (true) {
gameHandler.update(0.016f);
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
return 0;
}
Step-by-Step Explanation
Phase 1: Auth Server Authentication
1.1 Connect to Auth Server
auth::AuthHandler authHandler;
authHandler.connect("logon.myserver.com", 3724);
What happens:
- TCP connection to auth server port 3724
- Connection state changes to
CONNECTED
1.2 Authenticate with SRP6a
authHandler.authenticate("MYACCOUNT", "mypassword");
What happens:
- Sends
LOGON_CHALLENGEpacket - Server responds with B, g, N, salt
- Computes SRP6a proof using password
- Sends
LOGON_PROOFpacket - Server verifies and returns M2
- Session key (40 bytes) is generated
Session Key Computation:
S = (B - k*g^x)^(a + u*x) mod N
K = Interleave(SHA1(even_bytes(S)), SHA1(odd_bytes(S)))
= 40 bytes
1.3 Request Realm List
authHandler.requestRealmList();
What happens:
- Sends
REALM_LISTpacket (5 bytes) - Server responds with realm data
- Parses realm name, address, population, etc.
Phase 2: Realm Selection
2.1 Parse Realm Address
const auto& realm = realms[0];
size_t colonPos = realm.address.find(':');
std::string host = realm.address.substr(0, colonPos);
uint16_t port = std::stoi(realm.address.substr(colonPos + 1));
Realm address format: "127.0.0.1:8085"
Phase 3: World Server Connection
3.1 Connect to World Server
game::GameHandler gameHandler;
gameHandler.connect(
host, // e.g., "127.0.0.1"
port, // e.g., 8085
sessionKey, // 40 bytes from auth server
accountName, // Same account
12340 // Build number
);
What happens:
- TCP connection to world server
- Generates random client seed
- Waits for
SMSG_AUTH_CHALLENGE
3.2 Handle SMSG_AUTH_CHALLENGE
Server sends (unencrypted):
Opcode: 0x01EC (SMSG_AUTH_CHALLENGE)
Data:
uint32 unknown1 (always 1)
uint32 serverSeed (random)
Client receives:
- Parses server seed
- Prepares to send authentication
3.3 Send CMSG_AUTH_SESSION
Client builds packet:
Opcode: 0x01ED (CMSG_AUTH_SESSION)
Data:
uint32 build (12340)
uint32 unknown (0)
string account (null-terminated, uppercase)
uint32 unknown (0)
uint32 clientSeed (random)
uint32 unknown (0) x5
uint8 authHash[20] (SHA1)
uint32 addonCRC (0)
Auth hash computation (CRITICAL):
SHA1(
account_name +
[0, 0, 0, 0] +
client_seed (4 bytes, little-endian) +
server_seed (4 bytes, little-endian) +
session_key (40 bytes)
)
Client sends:
- Packet sent unencrypted
3.4 Initialize Encryption
IMMEDIATELY after sending CMSG_AUTH_SESSION:
socket->initEncryption(sessionKey);
What happens:
1. encryptHash = HMAC-SHA1(ENCRYPT_KEY, sessionKey) // 20 bytes
2. decryptHash = HMAC-SHA1(DECRYPT_KEY, sessionKey) // 20 bytes
3. encryptCipher = RC4(encryptHash)
4. decryptCipher = RC4(decryptHash)
5. encryptCipher.drop(1024) // Drop first 1024 bytes
6. decryptCipher.drop(1024) // Drop first 1024 bytes
7. encryptionEnabled = true
Hardcoded Keys (WoW 3.3.5a):
ENCRYPT_KEY = {0xC2, 0xB3, 0x72, 0x3C, 0xC6, 0xAE, 0xD9, 0xB5,
0x34, 0x3C, 0x53, 0xEE, 0x2F, 0x43, 0x67, 0xCE};
DECRYPT_KEY = {0xCC, 0x98, 0xAE, 0x04, 0xE8, 0x97, 0xEA, 0xCA,
0x12, 0xDD, 0xC0, 0x93, 0x42, 0x91, 0x53, 0x57};
3.5 Handle SMSG_AUTH_RESPONSE
Server sends (ENCRYPTED header):
Header (4 bytes, encrypted):
uint16 size (big-endian)
uint16 opcode 0x01EE (big-endian)
Body (1 byte, plaintext):
uint8 result (0x00 = success)
Client receives:
- Decrypts header with RC4
- Parses result code
- If 0x00: SUCCESS!
- Otherwise: Error message
Phase 4: Ready for Game
At this point:
- ✅ Session established
- ✅ Encryption active
- ✅ All future packets have encrypted headers
- 🎯 Ready for character operations
Error Handling
Auth Server Errors
authHandler.setOnFailure([](const std::string& reason) {
// Possible reasons:
// - "ACCOUNT_INVALID"
// - "PASSWORD_INVALID"
// - "ALREADY_ONLINE"
// - "BUILD_INVALID"
// etc.
});
World Server Errors
gameHandler.setOnFailure([](const std::string& reason) {
// Possible reasons:
// - "Connection failed"
// - "Authentication failed: ALREADY_LOGGING_IN"
// - "Authentication failed: SESSION_EXPIRED"
// etc.
});
Testing
Unit Test Example
void testCompleteAuthFlow() {
// Mock auth server
MockAuthServer authServer(3724);
// Real auth handler
auth::AuthHandler auth;
auth.connect("127.0.0.1", 3724);
bool success = false;
std::vector<uint8_t> key;
auth.setOnSuccess([&](const std::vector<uint8_t>& sessionKey) {
success = true;
key = sessionKey;
});
auth.authenticate("TEST", "TEST");
// Wait for result
while (auth.getState() == auth::AuthState::CHALLENGE_SENT ||
auth.getState() == auth::AuthState::PROOF_SENT) {
auth.update(0.016f);
}
assert(success);
assert(key.size() == 40);
// Now test world server
MockWorldServer worldServer(8085);
game::GameHandler game;
game.connect("127.0.0.1", 8085, key, "TEST", 12340);
bool worldSuccess = false;
game.setOnSuccess([&worldSuccess]() {
worldSuccess = true;
});
while (game.getState() != game::WorldState::READY &&
game.getState() != game::WorldState::FAILED) {
game.update(0.016f);
}
assert(worldSuccess);
}
Common Issues
1. "Invalid session key size"
Cause: Session key from auth server is not 40 bytes
Solution: Verify SRP implementation. Session key must be exactly 40 bytes (interleaved SHA1 hashes).
2. "Authentication failed: ALREADY_LOGGING_IN"
Cause: Character already logged in on world server
Solution: Wait or restart world server.
3. Encryption Mismatch
Symptoms: World server disconnects after CMSG_AUTH_SESSION
Cause: Encryption initialized at wrong time or with wrong key
Solution: Ensure encryption is initialized AFTER sending CMSG_AUTH_SESSION but BEFORE receiving SMSG_AUTH_RESPONSE.
4. Auth Hash Mismatch
Symptoms: SMSG_AUTH_RESPONSE returns error code
Cause: SHA1 hash computed incorrectly
Solution: Verify hash computation:
// Must be exact order:
1. Account name (string bytes)
2. Four null bytes [0,0,0,0]
3. Client seed (4 bytes, little-endian)
4. Server seed (4 bytes, little-endian)
5. Session key (40 bytes)
Next Steps
After successful world authentication:
-
Character Enumeration
// Send CMSG_CHAR_ENUM (0x0037) // Receive SMSG_CHAR_ENUM (0x003B) // Display character list -
Enter World
// Send CMSG_PLAYER_LOGIN (0x003D) with character GUID // Receive SMSG_LOGIN_VERIFY_WORLD (0x0236) // Now in game! -
Game Packets
- Movement (CMSG_MOVE_*)
- Chat (CMSG_MESSAGECHAT)
- Spells (CMSG_CAST_SPELL)
- etc.
Summary
This guide demonstrates the complete authentication flow from auth server to world server:
- ✅ Auth Server: SRP6a authentication → Session key
- ✅ Realm List: Request and parse realm data
- ✅ World Server: RC4-encrypted authentication
- ✅ Ready: All protocols implemented and working
The client is now ready for character operations and world entry! 🎮
Implementation Status: 100% Complete for authentication Next Milestone: Character enumeration and world entry