看到 token expired 時,我不會修改 payload 裡的 exp、重新 Base64URL encode,或把 server 的 expiration check 關掉。signed JWT 的 header/payload 一旦改變,原本 signature 就不再對應;而停用 expiration 只是把 credential lifecycle 的安全邊界拿掉。
我的處理方式是:先確認真的過期,再走系統原本設計的 refresh 或重新登入流程。
先確認 exp 與目前時間
我會用 JWT Expiry Checker 查看 exp、iat、nbf。
JavaScript:
const expired = Date.now() >= payload.exp * 1000;
記得 JWT NumericDate 是秒、Date.now() 是毫秒。
如果剛取得的 token 馬上被判定過期,我會先檢查 seconds/milliseconds 與 server clock,而不是立刻認定 issuer 發錯 token。
Access token 與 refresh token 是不同 credential
常見 OAuth 流程中:
- access token 用來呼叫 resource API;
- refresh token 用來向 token endpoint 換新的 access token。
refresh token 本身不一定是 JWT。即使它長得像 JWT,我也會依 protocol/provider 定義確認用途。
正常 refresh 流程
概念上:
expired access token
↓
valid refresh token
↓
token endpoint
↓
new access token
如果 refresh token 也失效、過期或被撤銷,就應回到 reauthentication,而不是放寬 verifier。
Refresh 成功卻一直 401,我找 stale state
這是很常見的 application bug:新 token 已拿到,但 API client 還在送舊的那一顆。
我會檢查:
- request interceptor 是否 capture 舊 token;
- React/store state 是否更新;
- 多 browser tab 是否不同步;
- server session/cache 是否還保留舊值;
- retry logic 是否重送原 request;
- service worker 是否影響 request。
最直接的方法是比較 Network 面板裡真正送出的 Bearer token exp,和 refresh response 的新 token 是否一致。
Refresh token rotation 也會造成下一次失敗
一些 provider 使用 refresh token rotation:每次 refresh 都回傳新的 refresh token,舊的可能立即失效。
我會確認:
- response 是否含新的 refresh token;
- application 是否 atomic 更新;
- concurrent refresh 是否互相覆蓋;
- reuse detection 是否被觸發。
如果只更新 access token、沒更新 rotated refresh token,下一輪 refresh 很可能失敗。
Client 不應單獨決定 security expiration
前端先看 exp 可以改善 UX,例如提早 refresh,但 resource server 仍必須自己驗證 expiration。使用者可以修改 browser clock 或 client code,所以 client-side check 不是 security boundary。
Token 沒過期仍 401 怎麼辦
那就不再把問題叫做「expired token」。我會繼續檢查:
- signature;
- issuer;
- audience;
nbf;- token type;
- scope/role;
- JWKS key rotation/cache。
完整流程見 JWT 401 Unauthorized。
Rotation / revocation 與 expiration 不一樣
即使 exp 還在未來,token 也可能因為:
- signing key 被 emergency revoke;
- server-side session 被關閉;
- denylist/revocation policy;
- refresh token family 被撤銷;
而提前失效。
反過來,JWT 自帶 exp 也不代表 logout 一定能立即使所有 token 失效,這取決於系統是否有額外 revocation model。
我不採用的修法
ignoreExpiration=true留在 production;- 修改
exp後繼續使用原 signature; - 因為 refresh 偶爾失敗就把 access token lifetime 無限拉長;
- 將長效 refresh token 隨意存進可被 XSS 讀取的 storage;
- 每個 401 都無限 refresh,造成 retry loop。
我會設計 refresh failure boundary
如果 refresh 失敗,我希望流程有清楚結束條件:
API 401
→ 判斷是否因 access token expiry
→ 最多一次受控 refresh
→ retry original request
→ 若仍失敗,回到登入/顯示錯誤
我不允許 recursive refresh loop。
我的 expired token 清單
exp是 number 且以秒解讀嗎?- issuer/verifier clock 正常嗎?
- 這是 access token 還是其他 token?
- 系統是否支援 refresh?
- refresh token 是否仍有效?
- refresh rotation 是否正確保存新 token?
- 新 access token 是否真的被後續 request 使用?
- 若仍 401,是否其實是 signature/issuer/audience 問題?
- expiration validation 是否仍在 server 強制執行?
我處理 expired JWT 的目標不是「把舊 token 救回來」,而是確認 整個 credential lifecycle——發行、到期、refresh、撤銷、重新登入——都按原本安全設計運作。