In 2026, several admin first-screen APIs return 401 at once—and even the refresh token request returns 401. Is the interceptor blocking itself?
Bottom line first: In 2026 Angular projects, when token expiry causes frantic retries, the refresh endpoint itself is usually not broken. Two things tend to stack up: the refresh token request is also intercepted by the same interceptor, creating a 401 → refresh → 401 recursion; and first-screen concurrent requests each receive 401 without queuing, triggering multiple refreshes at the same time. A more stable approach is to let the refresh request use a separate HttpClient or skip it via an allowlist, allow only one refresh in flight for concurrent 401s, suspend the other requests, and replay them after the new token arrives.
First, look at three trigger points—don't stare only at the refresh endpoint
Many teams first check the refresh endpoint's return value, but in delivery contexts it is more common that the interceptor does not distinguish between business requests and refresh requests. Locate the trigger points first, then talk about queuing; this saves half the detours.
- The refresh request is intercepted by itself: The refresh token uses the same HttpClient, the interceptor treats it as a request that needs authentication, and the 401 handling logic calls itself.
- Concurrent 401s each refresh: An admin first screen commonly sends 6–8 APIs at the same time; after the old token expires, they all return 401, each calls the refresh endpoint, and the results overwrite one another.
- No stop after refresh failure: When the refresh endpoint itself fails, the fallback logic triggers a retry or route navigation, the route guard sends another request, and another loop forms.
To judge whether these three issues are present, check the browser network panel: if the refresh endpoint is called multiple times in a short period, or the refresh endpoint itself also returns 401, you can basically confirm that interceptor recursion and unqueued concurrency exist at the same time.
Delivery context: concurrent 401s on the first screen, user kicked out repeatedly
In an admin management system project, the constraints were typical: the dashboard first screen sent 6–8 concurrent APIs, the token lifetime was short, and in the integration environment old tokens expired and returned 401 in a burst. The original interceptor called the refresh endpoint directly for every 401, and the refresh request also went through the same HttpClient. Strings of 401s soon appeared in the logs, and the user was sent to the login page three times in a row.
The later approach was to create a separate HttpClient instance without that interceptor for refresh requests, and to skip refresh and login endpoints in the interceptor via a URL allowlist. Concurrent 401s used a single-flight Observable plus a wait queue, with the queue timeout set in the experience range of 8–15 seconds. The result was that concurrent 401s triggered only one refresh, the original requests replayed successfully, and the user was no longer kicked out repeatedly. The cost is an extra layer of state management in the interceptor; colleagues who take over later need to read the comments first, otherwise they may delete the allowlist and the problem will return.
Choosing among three approaches: don't add a queue for just a couple of endpoints
No approach is absolutely better; the key is concurrency volume and token lifecycle. The three common approaches in 2026 are compared below. Effort is an experience range and depends on the number of interceptors and the concurrency scenario.
- Approach A: refresh directly inside the interceptor: typical range 0.5–1 person-day; suitable for small projects with few endpoints, low concurrency, and a token lifetime that covers a single session. Not suitable for admin systems with many concurrent first-screen APIs or very short tokens.
- Approach B: separate auth service plus single-flight queue: typical range 1–3 person-days; suitable for admin systems with concurrent APIs and a need for silent renewal. It requires maintaining the queue, timeout, and failure fallback, so complexity is higher.
- Approach C: pre-refresh in the route guard: typical range 0.5–1 person-day; suitable for renewal before page-level navigation. Not suitable for scenarios with long stays on a page, timed polling, or consecutive requests inside a modal.
If the project already has a unified auth service, put the single-flight logic in the service first rather than scattering it in the interceptor. If it is just a few public pages plus one login state, Approach A with allowlist skipping is usually enough, and there is no need to introduce extra state for queuing.
Acceptance checks before release
Acceptance for 401 handling cannot only check whether refresh works; it must check concurrency and failure paths. The following items can serve as testable criteria.
- When 5 concurrent requests receive 401 at the same time, the refresh endpoint is called only once, and the other requests wait in the queue.
- The refresh request does not appear in the interceptor's 401 recursion logs; when the refresh endpoint itself returns 401, no further refresh is triggered.
- After a refresh failure, the login page is navigated to only once; requests in the wait queue fail and return directly without repeated navigation.
- Replayed requests keep the original method, body, and business headers, and only Authorization is replaced; POST requests do not end up with an empty body.
- The wait queue has a timeout, typically 8–15 seconds, so the page does not spin forever when the refresh endpoint hangs.
Common pitfalls cluster around the allowlist and replay. If the allowlist matches by URL substring too broadly, it will also skip business endpoints. During replay, a newly created request may carry only the URL and headers, miss the body, and turn POST into an empty submission. Another frequent issue is not updating local storage after a successful refresh, so when the user manually reloads the page they get the old token again.
Applicable and non-applicable boundaries
Situations suitable for single-flight refresh and a wait queue: an admin system with many concurrent first-screen APIs, a short token lifetime, a need for silent renewal for users, and a refresh endpoint that itself requires authentication. Public content pages and sites based mainly on Cookie sessions usually do not need to copy this.
The unsuitable cases should also be stated clearly: a small project where the token lifetime covers a single use and there are very few endpoints—adding a queue increases maintenance cost instead. When a page has only one or two requests and concurrent 401s are rare, allowlist skipping is the more cost-effective first step.
Boundary sentence: If the project has only 1–2 endpoints and the token lifetime covers a single session, first use an allowlist to skip the refresh request; a single-flight queue is not mandatory. Whether to upgrade to a queue depends on whether concurrent 401s actually occur and whether they cause duplicate refreshes or repeated logouts, not on the framework version.
FAQ
In an Angular interceptor, how do I keep the refresh token request from being intercepted again?
A common approach is to create a separate HttpClient instance without that interceptor for the refresh request, or skip it in the interceptor via a URL allowlist and a custom header. Choose one; do not rely only on URL substring matching.
When multiple APIs return 401 at the same time, how do I ensure the token is refreshed only once?
Use a shared refreshInFlight Observable for single-flight. The first 401 starts the refresh, and the other requests subscribe to the same stream. When the new token arrives, they replay, avoiding separate calls to the refresh endpoint.
After refresh token failure, why does the page navigate to the login page several times in a row?
Concurrent 401s each take the failure branch, and each performs navigation. Add a redirect flag to the fallback logic, or let only the refresh initiator handle navigation while the other waiting requests fail and return directly.
When replaying the original request in the interceptor, why is the POST body lost?
It is common to create a new request during replay with only the URL and headers, without carrying over the original request.body and method. Using clone and explicitly setting the body and setHeaders is more reliable.
Is there a difference in handling 401 between HttpInterceptorFn and class-based interceptors?
Functional interceptors use inject to get dependencies, so single-flight state should live in an injectable service rather than a module-level variable. Class-based interceptors follow the same principle; the main difference is syntax, while the concurrent queuing logic is the same.
If you are going to change an existing interceptor next, first do a code walkthrough in four steps—identify, queue, replay, fallback—and then validate with an integration scenario of 5 concurrent 401s. State the applicable boundaries in advance as well: public pages and Cookie-session sites do not need to copy the single-flight queue; choose based on actual concurrency and token lifecycle.
-
In 2026, clicking “Get verification code” in a modal submits the whole form too—is the button’s default type the culprit?
Date: Oct 30, 2026 Read: 4
-
In 2026, AI can ship a homepage fast, but images break and buttons stop responding on target devices—should frontend still own compatibility and performance?
Date: Oct 29, 2026 Read: 24
-
It's 2026—when my performance review asks what separates a frontend experience architect from a regular frontend developer, what evidence actually counts?
Date: Oct 28, 2026 Read: 19
-
It's 2026—why does a campaign page countdown fall behind after a few minutes on the lock screen, and why do users send screenshots asking if we cut their time?
Date: Oct 27, 2026 Read: 21
-
In 2026, why does a Uni-app Android app ask me to log in again after I kill and reopen the process, while the WeChat Mini Program still recognizes me—aren't the caches on both sides the same place?
Date: Oct 26, 2026 Read: 23




