關於 AuthContext 在登入時會將舊 token 初始為 undefined 問題 #260
Replies: 2 comments 1 reply
-
Hi @ttpss930141011, 謝謝你 trace 了整段 refresh token 流程,但我沒有看到造成 root cause(token 是 undefined)的原因。
延伸議題:如果要防止下一次遇到 token 是 undefined,是不是要寫測試來保證此段邏輯功能正確性? |
Beta Was this translation helpful? Give feedback.
-
Hello 各位, 先整理幾個背景狀況提前說明:
一、為何該 API 會在沒有 token 的情況下發出請求 觀察目前
可以發現是透過 useEffect 呼叫 fetch 函式,該函式先嘗試透過 cookie 取得 jwtToken,若有成功取得,則執行 API,並將回傳結果賦值給 authContext 的 token,倘若失敗則將 token 設為 null。 此處個人的看法是,cookie 中有留存 jwtToken 並不代表 authContext 裡的 token 也一定有值,回顧背景狀況第 2 點,API 夾帶的 token 只會從 authContext 取得,因此這裡尚未針對 「cookie 有存 jwtToken 但 authContext 的 token 不存在」的情境進行例外處理,是造成此 API 帶到 undefined 的第一層原因。 然於背景狀況第 3 點有說明,除執行 authentication API 之外,還有登入這個時機點可以更新 authContext 的 token,邏輯上從登入頁登入後,在 [token] 路由的元件使用 setToken 賦值,再經由 先接續背景狀況第 4 點,本專案有實作讓使用者即使離開網站依然可以保留離開前的 token 的機制,因此造訪網站的使用者不一定會走登入流程來驗證身分並更新 authContext 的 token。 但是,相同的程式碼就會得到相同的結果,依照目前的解讀,在這種情形下,雖然 cookie 保留了 token,但此時 authContext 的 token 尚未有值 (undefined) ,因此請求 authentication API 時,依然會帶到 undefined 的 token,故個人認為解決 API 誤帶 undefined 的議題,可以先從這裡下手。
在 React 中,狀態的更新可能是非同步的 (參考官方範例),在同一個函式使用 setToken,並不會立即讓下一行程式碼訪問到最新的 token ,因此上述程式碼異動的重點是,強制讓此 useEffect 至少執行兩次,第一次嘗試從 cookie 拿到 jwtToken,並使用 setToken 更新狀態,等到第二次進來時,就能保證 token 是更新過後的並依此 token 執行身分驗證,開發模式的結果截圖如下,可以觀察到這次 API 正確帶入了開發模式下模擬的 token: login-token : 上述程式碼尚有未深入思考之處,譬如額外用了一組似乎可避免的狀態來控制流程,以及 useEffect 的依賴陣列沒有如實納入相關變數與函式等等,因此不適合納進主分支作為解決方案,但想說明的是 authentication API 誤帶 undefined token 的議題,是有可能透過調整 二、為何在該時機點 authContext 裡的 token 會是 undefined 這個議題反覆測試了好幾次,目前沒有肯定的答案,只有一些線索可以分享:
目前可能可以從兩個方向測試或釐清,第一是,是否有可能為 Next.js 的 prerender 機制的因素,裡面提到 「By default, Next.js pre-renders every page. This means that Next.js generates HTML for each page in advance, instead of having it all done by client-side JavaScript.」,第二是往前追朔以前的 commit,確認是否在專案之初,每次切換路由就會造成 context們重置。 最後 Justin 提到的 Zustand 狀態管理套件,我覺得可以考慮,原因是個人如果要改寫 以上說明,供參考,謝謝! |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
前言
Branch
這次的 branch:
https://github.com/Game-as-a-Service/Game-Lobby-Web/tree/fix/justin-test
情況
搭好,前幾日在 Discord 出現了 token === undefined 的 issue,但在我印象中在登入後 token 就會先被設定一個值,沒道理在 useRequest 時 token 又等於undefined,研究了一下之後發現 AuthContext 在拿到一個預設 token 之後,被設定成 undefined (但並沒有程式碼去setToken(undefined)
Root cause
原先帶有舊 token 的 AuthContext,在 Startup 組件在打出 authenticate request 前被設定成 undefined,導致有此次 issue。
我的猜想
我猜想 AuthContext 被重新初始化了,但我不太清楚為什麼 useContext 會在這個奇怪的時間點進行重新初始化,問了 GPT 後他給我了這個答案:
但是在登入跳轉時應該不會導致 token 丟失,讓我很困惑 useContext 重置的機制,所以特地向各位尋求幫助,但整個問題的前後文很冗長,我嘗試優化了一下內容,希望這樣更清楚明瞭,並請各位大大不吝賜教。
登入流程
由於登入流程比較複雜,大致交代流程:
Development
Production
GET / https://api.gaas.waterballsa.tw/login?type=google-oauth2
並讓 server 轉到 Google 那https://lobby.gaas.waterballsa.tw/auth/token/${token}
POST / authenticate
實作成 protected API 導致 401,無法像在 Development 環境時拿到新 token。Zustand 替換 useContext
在 fix/justin-test 這個 branch 中,我有用一個輕量 state management tool Zustand 去做原先 AuthContext 在做的事情,並把他持久化在 sessionStorage 裡面,解決了上述的 token 變 undefined 的問題:

有興趣的話可以在 hooks\context\useAuth.ts 中將 useContext 替換成註解起來的 useAuthStore 看看效果
想討論的議題
Note: 我先前有實作了 Zustand 整合 Socket.io 的小 demo,用起來差不多像是這樣子:frontend/src/store/socket.ts
Beta Was this translation helpful? Give feedback.
All reactions