Lala Code

Lala 的前端大補帖,歡迎一起鑽研前端技術😊

0%

從 Token 驗證到 Refresh 自動續期:前端攔截器的進階應用

在開發需要登入的前端應用時,如何安全又順暢地處理 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; // 提前 10 分鐘刷新

if (now >= expireTime) {
try {
const res = await api.auth.postRefreshToken();
if (res.isSuccess) {
const { jwtToken } = res.data;
localStorage.setItem('userToken', jwtToken); // ✅ 更新 token
} 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 錯誤彈窗,並導回登入頁。



Step 4:加上 Authorization 與 x-api-key Header

最後,我們為請求加上標準的 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 {
// token 逾期動作,可自行調整
window.dispatchEvent(new CustomEvent(EventTypes.TIMEOUT));
return Promise.reject(new Error('JWT token expired'));
}
} catch (err) {
// token 逾期動作,可自行調整
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

Hahow 有各式各樣類型的課程,而且是無限次數觀看,對學生或上班族而言,不用擔心被時間綁住



六角學院

如果你是初學者,非常推薦六角學院哦!
剛開始轉職也是上了六角的課,非常的淺顯易懂,最重要的是,隨時還有線上的助教幫你解決問題!


Udemy

Udemy 裡的課程非常的多,品質普遍不錯,且價格都滿實惠的,CP值很高!
也是很多工程師推薦的線上課程網站。
❤️