在開發需要登入的前端應用時,如何安全又順暢地處理 JWT Token 是每個前端都會遇到的課題。
這篇文章會一步步拆解我的實作邏輯,讓你的 API 請求具備:
✅ 自動攔截與驗證 Token
✅ Token 過期自動續期(refresh token)
✅ 錯誤時觸發系統級彈窗
✅ 加上 x-api-key 強化安全
Step 1:排除不需要 Token 的 API
有些 API 是不需要驗證登入狀態的,例如登入、註冊、驗證碼等,這些我們統一列在 publicApiPaths 裡。
1 2 3
| const url = config.url || ''; const isPublicApi = publicApiPaths.some((api) => url.includes(api)); if (isPublicApi) return config;
|
只要請求 URL 有包含清單內的項目,就不進行驗證,直接放行。
Step 2:取得 Token 與過期時間
接下來,我們從 localStorage 抓取使用者的 Token,並使用工具函式 getTokenExpiration(token) 解析出過期時間。
1 2 3 4 5 6 7 8 9 10 11
| const token = localStorage.getItem('userToken'); if (!token) { window.dispatchEvent(new CustomEvent(EventTypes.SYSTEM_ERROR)); return Promise.reject(new Error('JWT token not found')); }
const exp = getTokenExpiration(token); if (!exp) { window.dispatchEvent(new CustomEvent(EventTypes.SYSTEM_ERROR)); return Promise.reject(new Error('JWT token expiration not found')); }
|
只要缺少任何一項資訊(Token 或過期時間),就會觸發系統錯誤彈窗,並中止請求。
Step 3:使用 Refresh Token 自動續期
如果目前時間已經超過 Token 的有效期前 10 分鐘,就自動發送 refresh token 請求,換一組新的 JWT,讓使用者無感延長 session:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const now = Math.floor(Date.now() / 1000); const expireTime = exp - 10 * 60;
if (now >= expireTime) { try { const res = await api.auth.postRefreshToken(); if (res.isSuccess) { const { jwtToken } = res.data; localStorage.setItem('userToken', jwtToken); } else { window.dispatchEvent(new CustomEvent(EventTypes.TIMEOUT)); return Promise.reject(new Error('JWT token expired')); } } catch (err) { window.dispatchEvent(new CustomEvent(EventTypes.TIMEOUT)); return Promise.reject(new Error('JWT token expired')); } }
|
如果 refresh 成功,就更新 localStorage 內的 token;如果 refresh 失敗,則觸發 TIMEOUT 錯誤彈窗,並導回登入頁。
最後,我們為請求加上標準的 JWT 驗證 header,以及一組 x-api-key:
1 2 3
| config.headers.set('Authorization', `Bearer ${token}`); config.headers.set('x-api-key', apiKey); return config;
|
補充:x-api-key 是什麼?為什麼要加?
x-api-key 是一個 自訂的 HTTP 標頭,通常後端會用來:
✅ 辨識請求是從哪個應用系統來的(App / 後台 / 前台…)
✅ 加強安全,避免陌生來源亂打 API
✅ 搭配 JWT 做更細的存取控管
這不是必加的欄位,是否需要依專案需求決定。但如果你有多端共享 API,或希望加強存取保護,會是一個實用做法。
完整攔截器程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| instance.interceptors.request.use(async (config) => { const url = config.url || ''; const isPublicApi = publicApiPaths.some((api) => url.includes(api)); if (isPublicApi) return config;
const token = localStorage.getItem('userToken'); if (!token) { window.dispatchEvent(new CustomEvent(EventTypes.SYSTEM_ERROR)); return Promise.reject(new Error('JWT token not found')); }
const exp = getTokenExpiration(token); if (!exp) { window.dispatchEvent(new CustomEvent(EventTypes.SYSTEM_ERROR)); return Promise.reject(new Error('JWT token expiration not found')); }
const now = Math.floor(Date.now() / 1000); const expireTime = exp - 10 * 60;
if (now >= expireTime) { try { const res = await api.auth.postRefreshToken(); if (res.isSuccess) { const { jwtToken } = res.data; localStorage.setItem('userToken', jwtToken); } else { window.dispatchEvent(new CustomEvent(EventTypes.TIMEOUT)); return Promise.reject(new Error('JWT token expired')); } } catch (err) { window.dispatchEvent(new CustomEvent(EventTypes.TIMEOUT)); return Promise.reject(new Error('JWT token expired')); } }
config.headers.set('Authorization', `Bearer ${localStorage.getItem('userToken')}`); config.headers.set('x-api-key', apiKey); return config; });
|
🚀線上課程分享
線上課程可以加速學習的時間,省去了不少看文件的時間XD,以下是我推薦的一些課程
想學習更多關於前後端的線上課程,可以參考看看。
Hahow 有各式各樣類型的課程,而且是無限次數觀看,對學生或上班族而言,不用擔心被時間綁住
如果你是初學者,非常推薦六角學院哦!
剛開始轉職也是上了六角的課,非常的淺顯易懂,最重要的是,隨時還有線上的助教幫你解決問題!
Udemy 裡的課程非常的多,品質普遍不錯,且價格都滿實惠的,CP值很高!
也是很多工程師推薦的線上課程網站。