GAGA SSO Mobile SDK — Developer Guide

Interactive reference for the engineer building the SDK · คู่มือเชิงโต้ตอบสำหรับนักพัฒนา SDK · open in any browser

Overview / ภาพรวม

The SDK is the only component the game calls. It owns the tokens, renders the auth/account UI, signs requests, and talks to the GAGA backend over HTTPS. The Gamebryo C++ engine reaches it through a thin C++ bridge.

Game ClientHonor of Heirs · Gamebryo (C++) → Gaga_* bridge
↓  JNI (Android) / Obj-C (iOS)
GAGA SDK (native)Android AAR · iOS XCFramework — token store, login UI, request signing
↓  HTTPS · Bearer token + HMAC
GAGA BackendAuth / SSO · Unified GAGA ID · Web Shop · Item-Code · Tickets · Logs
Social IdP — Google · FB · ApplePayment GWGame Server (Add-Item)

One Unified GAGA ID per player. The same account & tokens work across Game Client, Website and Web Shop (true SSO). Desktop SDK is out of SOW, but auth is plain REST so a PC client can reuse the C++ bridge + web login later — platform accepts android | ios | pc | web.

Login methods / วิธีล็อกอินและ UI

The most important thing to get right: each method shows a different kind of UI. Guest and social are native; GAGA ID (email) is a web-hosted page in the system browser — never a native form or embedded WebView.

UI: Native launcher · no form


  

Web-login flow / OAuth2 + PKCE

How the GAGA ID button works end to end. The full mechanism is below, then step through it one message at a time.

Sequence · OAuth2 Authorization Code + PKCE
OAuth2 Authorization Code with PKCE sequenceGame and SDK open the system browser to the GAGA hosted login; the auth service returns a one-time code via deep link; the SDK exchanges it with the PKCE verifier at /auth/token for tokens.Game + SDKSystem BrowserGAGA Login (web)Auth ServiceSDK generates PKCE code_verifier + code_challengeopen system browser (ASWebAuthSession / Custom Tabs)GET /login?client=hoh&channel=game&code_challenge=…email/pw · register · forgot · 2FA OTP · consent (hosted, shared with Web Shop)authenticate (GAGA ID credentials)OK — issue one-time auth code302 redirect gagasdk://auth/callback?code=…deep link returns code to SDKPOST /auth/token {code, code_verifier} (PKCE)200 {gaga_id, access_token, refresh_token}

Solid blue = request, dashed grey = response. The verifier stays on the device; only the challenge is sent at /login, and the verifier is revealed only at /auth/token.

Inside step 1 · the PKCE pair

Two values the SDK generates fresh for every login — they prove the app that started the flow is the same one finishing it. This is what protects a public client (a mobile app can't safely hold a client secret).

code_verifier = a high-entropy random string that never leaves the device. code_challenge = its SHA-256 hash, safe to send out because a hash can't be reversed.

code_verifier  = base64url( random(32 bytes) )        // 43 chars, no padding
code_challenge = base64url( SHA256(code_verifier) )   // code_challenge_method = "S256"

Where each value goes in the flow

Step 2  GET /login?...&code_challenge=<challenge>&code_challenge_method=S256   ← challenge only
Step 6  POST /auth/token { code, code_verifier }                              ← verifier
        backend recomputes SHA256(verifier), compares to the stored challenge

Why it matters on mobile: the callback returns on a deep link (gagasdk://auth/callback?code=…) that a malicious app could intercept. Without PKCE, a stolen code could be exchanged for tokens. With PKCE it can't — the attacker has no code_verifier and the hash can't be reversed, so the stolen code is worthless.

Show generate code (Kotlin / Swift)

Android · Kotlin

val verifier = Base64.encodeToString(ByteArray(32).also { SecureRandom().nextBytes(it) },
    Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
val challenge = Base64.encodeToString(
    MessageDigest.getInstance("SHA-256").digest(verifier.toByteArray()),
    Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)

iOS · Swift (CryptoKit)

var b = [UInt8](repeating: 0, count: 32); _ = SecRandomCopyBytes(kSecRandomDefault, 32, &b)
let verifier = Data(b).base64URLEncodedString()
let challenge = Data(SHA256.hash(data: Data(verifier.utf8))).base64URLEncodedString()

Do: use S256 (never plain) · a new pair per login · keep the verifier in memory only · also send a random state for CSRF. Don't: log, persist, or reuse the verifier.

Binding & conflict / ผูกบัญชี guest → social

Converting a guest into a permanent account. The key branch: if the target social account already owns a character, the backend returns 409 BIND_CONFLICT and the SDK must show a resolution dialog — never overwrite silently.

Rule: a guest must bind before any Web Shop purchase

step

Tokens & sessions / โมเดล Token

Access tokenRS256 JWT, ~30 min, carries gaga_id + scopes. Verified statelessly.
Refresh tokenOpaque, rotated on every refresh. Revocation list in Redis (logout/ban).
Remember MeSilent refresh on launch — no re-login until refresh expiry/revoke.
Secure storageAndroid Keystore / iOS Keychain. Never plain prefs or logs.
Write callsHMAC-sign (X-Signature + X-Timestamp) to prevent tampering.
On 401Silent refresh, then retry the request once. No user re-login.

Setup checklist / เช็กลิสต์การตั้งค่า

Tap to tick — your progress is saved in this browser. The native plumbing the SDK depends on.

API & SDK reference / อ้างอิง API และเมธอด

EndpointSDK methodPurpose

Draft for technical alignment between PRANEAT (platform) and X-Legend (game). Endpoint paths, payloads and method names are proposals to finalize in the OpenAPI spec. Companion documents: SDK Integration Spec and Platform Technical Build Spec. v1.0 · 16 Jun 2026 · Confidential.