JWT拦截器逻辑疑问:两次400响应后的Token刷新处理咨询
Great question—this is such a common pitfall when building JWT-based auth flows, and your initial breakdown of the process is totally on point. Let’s dive into what happens at step 5 and how to break that frustrating loop.
What Happens Without Guardrails?
If you don’t add specific checks to your interceptor, yes, you will end up in an infinite loop. Here’s why: your interceptor sees the 400 response from the original request, triggers the refresh token call. But when the refresh token itself is expired, the server sends another 400. Your interceptor, without context, will try to refresh again… and again… forever.
How to Break the Loop (Proven Solutions)
You need to add safeguards to your interceptor to distinguish between "regular token expired" and "refresh token expired" scenarios, and prevent the interceptor from intercepting its own refresh request. Here are the most effective approaches:
Flag refresh requests to skip interception
When you send a request to the refresh token endpoint, add a custom flag (like_isRefreshRequest: truein the request config) so your interceptor knows to ignore responses from this specific call. This stops the refresh failure from triggering another refresh attempt.Limit refresh attempts
Add a simple counter that tracks how many times you’ve tried to refresh. Set a hard limit (usually 1 attempt is enough)—if the first refresh fails, you know the refresh token is invalid/expired, so you stop trying immediately.Use granular error codes instead of generic 400s
Instead of returning a plain 400 for both token and refresh token expiry, have your server return specific error codes (e.g.,token_expiredvsrefresh_token_expired). Your interceptor can then react differently:- For
token_expired: Trigger the refresh flow. - For
refresh_token_expired: Immediately redirect the user to login, since there’s no way to get a valid token without re-authenticating.
- For
Example Interceptor Implementation (Axios)
Here’s a simplified example of how to put this all together in a frontend interceptor:
let isRefreshing = false; let refreshAttempts = 0; const MAX_REFRESH_TRIES = 1; axios.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; const errorCode = error.response?.data?.code; // Only attempt refresh if: // 1. It's a regular token expiry error // 2. We haven't exceeded max attempts // 3. This isn't already a refresh request if ( error.response.status === 400 && errorCode === "token_expired" && !originalRequest._isRefreshRequest && refreshAttempts < MAX_REFRESH_TRIES ) { isRefreshing = true; refreshAttempts++; try { // Send refresh request with a flag to skip interception const refreshRes = await axios.post( "/api/auth/refresh-token", {}, { _isRefreshRequest: true } ); // Update stored tokens localStorage.setItem("accessToken", refreshRes.data.accessToken); // Retry original request with new token originalRequest.headers.Authorization = `Bearer ${refreshRes.data.accessToken}`; refreshAttempts = 0; // Reset counter for future requests isRefreshing = false; return axios(originalRequest); } catch (refreshError) { // Refresh failed—refresh token is expired/invalid isRefreshing = false; refreshAttempts = 0; // Redirect user to login page window.location.href = "/login"; return Promise.reject(refreshError); } } // If it's a refresh token expiry error, skip refresh and redirect if (errorCode === "refresh_token_expired") { window.location.href = "/login"; return Promise.reject(error); } // For all other errors, just reject normally return Promise.reject(error); } );
Key Takeaway
The core idea is to give your interceptor clear rules to know when to attempt a refresh, when to stop, and when to force a re-login. Combining request flags, attempt limits, and granular error codes ensures you avoid infinite loops while keeping the auth flow smooth for users.
内容的提问来源于stack exchange,提问作者ValRob

