Files
a620906209 d9f0fe11c6 security(auth): P1 Token Refresh + sessionStorage
- 新增三個 refresh 端點(member/provider/admin),revoke 舊 token 發行新 7 天 token
- axios.js / coachAxios.js:401 interceptor 改為 refresh-then-retry,含 isRefreshing queue 防並發、isRedirecting flag 防雙重 redirect
- auth.js / coachAuth.js / AuthCallbackView.vue:localStorage 全改為 sessionStorage
- echo.js / notificationAxios.js:同步改為 sessionStorage
- notifications.js:401 時停止 polling(stopPolling),不印 error log
- 新增 TokenRefreshTest.php(11 tests / 24 assertions):refresh 成功/失效/跨角色 403

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 04:33:35 +08:00

3.4 KiB
Raw Permalink Blame History

1. 後端 Refresh 端點

  • 1.1 [後端] 在 app/Http/Controllers/API/AuthController.php 新增 refreshMember(Request $request):驗證 role=memberrevoke 當前 token,發行新 token,回傳 { status: true, data: { token, token_type: "Bearer" } }
  • 1.2 [後端] 同上新增 refreshProvider(Request $request):驗證 role=providerrevoke + 發新 token
  • 1.3 [後端] 同上新增 refreshAdmin(Request $request):驗證 role=adminrevoke + 發新 token
  • 1.4 [後端] 在 routes/api.php 新增三個 refresh 路由(需 auth:sanctum middleware):POST /api/member/refreshPOST /api/provider/refreshPOST /api/admin/refresh
  • 1.5 [後端] 執行 php artisan route:clear 確認路由生效

2. 前端 Interceptor 改寫(Member

  • 2.1 [前端] 改寫 frontend/src/api/axios.jsresponse interceptor 改為 refresh-then-retry 邏輯,加入 isRefreshing flag 與 pendingRequests queue 防止並發重複 refresh
  • 2.2 [前端] 確認 axios.js 的 request interceptor 改從 sessionStorage 讀取 token(非 localStorage

3. 前端 Interceptor 改寫(Coach

  • 3.1 [前端] 改寫 frontend/src/api/coachAxios.jsresponse interceptor 改為 refresh-then-retry,呼叫 POST /api/provider/refresh,含 isRefreshing + queue 邏輯
  • 3.2 [前端] 確認 coachAxios.js 的 request interceptor 改從 sessionStorage 讀取 coach_token

4. 前端 Store 改為 sessionStorage

  • 4.1 [前端] 修改 frontend/src/stores/auth.jsinit()setAuth()logout() 中所有 localStorage 改為 sessionStoragekeys: tokenuser
  • 4.2 [前端] 修改 frontend/src/stores/coachAuth.js:同上,keys: coach_tokencoach_user
  • 4.3 [前端] 修改 frontend/src/views/AuthCallbackView.vue:第 21 行 localStorage.setItem('token', token) 改為 sessionStorage.setItem('token', token)

5. 自動化測試

  • 5.1 [測試] 新增 tests/Feature/TokenRefreshTest.php:驗證 POST /api/member/refresh 以有效 token 回傳新 token,且舊 token 同時失效(再打一次原 token 回 401)
  • 5.2 [測試] 同檔案補充:/api/provider/refresh/api/admin/refresh 相同行為;跨角色呼叫(member token 打 provider/refresh)回 403
  • 5.3 [測試] 同檔案補充:過期 token 呼叫 refresh 回 401;已 revoke token 呼叫 refresh 回 401

6. 手動驗證

  • 6.1 [整合測試] Token 過期行為確認:將 expires_at 改成過去時間,打 API 確認被踢回 /loginrefresh 端點同樣受 auth:sanctum 保護,過期 token 無法 refresh,此為預期行為。Sliding window 由 7 天有效期自然實現,主動使用期間不會到期)
  • 6.2 [整合測試] 多並發 401 只 refresh 一次:DevTools Network 確認只有一個 /refresh 請求,其他請求等待後以新 token 完成
  • 6.3 [整合測試] Refresh 失敗全部請求正確 rejectrevoke 所有 token 後觸發 API,確認被踢回登入頁且沒有無限 loop
  • 6.4 [整合測試] OAuth callback 改為 sessionStorageGoogle 登入後用 DevTools Application 確認 token 在 sessionStorage(非 localStorage
  • 6.5 [整合測試] 關閉分頁後 token 消失:登入後關閉分頁重開,確認 sessionStorage 已清空需重新登入
  • 6.6 [整合測試] Role endpoint 對應正確:member token 打 /api/provider/refresh 回 403,不會混用