iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Vibe Coding

上岸用AI,看小白如何從無到有的用Vibe Codeing開發遊戲系列 第 17 篇

Day 17:LINE LIFF 門市免下載體驗與 OAuth2 / JWT 雙重身分鑑權深度架構

  • 分享至 

  • xImage
  •  

歡迎來到第十七天!在前幾天完成了遊戲大廳、兩款核心小遊戲與後端防弊機制後,今天我們要解決一個非常接地氣的商業難題:「顧客在實體門市裡,如何以最短的路徑進入遊戲,並完成會員身分綁定?」

如果要求顧客在店裡下載 100MB 以上的原生 App,放棄率通常高達 80% 以上。因此,我們選擇了 LINE LIFF (LINE Front-end Framework) 作為載體。顧客只需要拿起手機掃描桌上的 QR Code,即可在 LINE 內建的 WebView 中瞬間啟動 Cocos 網頁遊戲。


1. OAuth2 Authorization Code Flow 與系統互動時序

為了保護使用者資料並防範偽造身分,我們採用了「LINE OAuth2 認證 + 本地 Ktor JWT 簽發」的雙重鑑權架構:

+------------------+          +-------------------+          +-------------------+          +------------------+
| 顧客手機 (LIFF)  |          | Kotlin Ktor 後端  |          |  LINE Platform   |          | PostgreSQL 資料庫|
+------------------+          +-------------------+          +-------------------+          +------------------+
         |                              |                              |                             |
         | --- (1) 初始化 LIFF SDK ----> |                              |                             |
         | <-- (2) 回傳 id_token -------|                              |                             |
         |                              |                              |                             |
         | --- (3) POST /api/v1/auth/login (idToken) -----------------> |                             |
         |                              | --- (4) 驗證 idToken --------> |                             |
         |                              | <-- (5) 回傳 LINE Profile -- |                             |
         |                              |                              |                             |
         |                              | --- (6) Upsert 使用者資料 -------------------------------> |
         |                              | <-- (7) 回傳 User Record --------------------------------- |
         |                              |                              |                             |
         | <-- (8) 簽發系統 JWT --------|                              |                             |

2. 前端 Cocos Creator (TypeScript) LIFF 整合實作

在 Cocos 大廳的初始化腳本 LiffManager.ts 中,我們處理 SDK 加載與 ID Token 取得,並發送至後端進行雙重鑑權:

import liff from '@line/liff';

export class LiffManager {
    private static jwtToken: string = "";

    public static async initLiffAndAuthenticate(): Promise<string> {
        try {
            // 1. 初始化 LINE LIFF SDK
            await liff.init({ liffId: "1234567890-AbcdEfgh" });

            // 2. 判斷使用者是否已在 LINE 中登入
            if (!liff.isLoggedIn()) {
                liff.login({ redirectUri: window.location.href });
                return "";
            }

            // 3. 取得 LINE ID Token (JWT 格式)
            const idToken = liff.getIDToken();
            if (!idToken) {
                throw new Error("無法取得 LINE ID Token");
            }

            // 4. 將 ID Token 發送給 Ktor 後端換取系統專屬 JWT
            this.jwtToken = await this.exchangeLineTokenForJwt(idToken);
            
            // 5. 將 JWT 儲存在 SessionStorage
            sessionStorage.setItem("APP_JWT_TOKEN", this.jwtToken);
            return this.jwtToken;

        } catch (error) {
            console.error("[LIFF Auth Error]", error);
            throw error;
        }
    }

    private static async exchangeLineTokenForJwt(idToken: string): Promise<string> {
        const response = await fetch("https://api.yourstore.com/api/v1/auth/login", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ idToken })
        });

        if (!response.ok) {
            throw new Error(`鑑權失敗 HTTP Status: ${response.status}`);
        }

        const resData = await response.json();
        return resData.data.token; // 回傳系統高權限 JWT
    }
}

3. 後端 Kotlin Ktor 雙重驗證與 JWT 簽發實作

在 Ktor 後端,我們透過 ktor-server-auth-jwt 與 ktor-client 實作嚴謹的雙重驗證,避免客戶端偽造身份:

// AuthRoutes.kt
fun Route.authRouting() {
    val httpClient = HttpClient(CIO) {
        install(ContentNegotiation) { json() }
    }

    post("/api/v1/auth/login") {
        val request = call.receive<LineAuthRequest>()
        
        // 1. 向 LINE 官方 Verify API 驗證 Token 合法性 (防止偽造 id_token)
        val lineVerifyResponse: HttpResponse = httpClient.post("https://api.line.me/oauth2/v2.1/verify") {
            setBody(FormDataContent(Parameters.build {
                append("id_token", request.idToken)
                append("client_id", System.getenv("LINE_CHANNEL_ID") ?: "")
            }))
        }

        if (lineVerifyResponse.status != HttpStatusCode.OK) {
            call.respond(HttpStatusCode.Unauthorized, "無效的 LINE ID Token")
            return@post
        }

        val verifyData = lineVerifyResponse.body<LineVerifyResult>()

        // 2. 資料庫 Upsert 操作 (若不存在則新增,若存在則更新登入時間)
        val user = UserService.findOrCreateByLineId(verifyData.sub, verifyData.name ?: "萌新業主")

        // 3. 簽發 7 天效期之 HMAC256 加密 JWT
        val token = JWT.create()
            .withAudience("my-store-game")
            .withIssuer("https://api.yourstore.com/")
            .withClaim("userId", user.id.toString())
            .withExpiresAt(Date(System.currentTimeMillis() + 7 * 86_400_000L))
            .sign(Algorithm.HMAC256("YOUR_JWT_SECRET_KEY"))

        call.respond(HttpStatusCode.OK, mapOf("code" to 200, "data" to mapOf("token" to token, "userId" to user.id)))
    }
}

💡 鐵人賽小知識:iOS Safari ITP 對 WebView 儲存機制的破壞與應對策略

Tech Tip:Apple 的 ITP (Intelligent Tracking Prevention) 隱私機制會限制第三方 WebView 中的 LocalStorage 與 Cookie 存留時間(常在關閉頁面或 7 天後自動清空)。為了防止顧客再次掃碼時掉登入,絕不能單純依賴持久化 Cookie!最佳實作是在每次進入 LIFF 時,永遠以 liff.getIDToken() 作為信任根基(Root of Trust),無感向 Ktor 後端換取最新 JWT,確保體驗完全不中斷!



上一篇
Day 16:雙遊戲平台場景切換與記憶體管理 —— Scene Manager、GC 釋放與 Object Pool
下一篇
Day 18:WebGL 極速載入與 60 FPS 順暢度 —— DrawCall 批次渲染、Auto Atlas 與 Brotli 靜態壓縮策略
系列文
上岸用AI,看小白如何從無到有的用Vibe Codeing開發遊戲 共 21 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言