Jump to content

Connect Leeroopedia MCP: Equip your AI agents to search best practices, build plans, verify code, diagnose failures, and look up hyperparameter defaults.

Heuristic:Langgenius Dify Token Refresh Loop Prevention

From Leeroopedia



Knowledge Sources
Domains Frontend, Authentication
Last Updated 2026-02-08 11:00 GMT

Overview

Authentication token refresh pattern that uses raw `globalThis.fetch()` instead of `baseFetch()` and cross-tab localStorage coordination to prevent infinite 401 loops and duplicate refresh requests.

Description

When an API request receives a 401 response, the frontend automatically attempts to refresh the authentication token. If the refresh endpoint itself also returns 401 and uses the same `baseFetch()` function (which intercepts 401 responses), it creates an infinite loop of refresh attempts. Additionally, in multi-tab browser scenarios, multiple tabs may simultaneously attempt to refresh the same token, causing race conditions and wasted requests.

Usage

Apply this heuristic whenever you work on authentication infrastructure in the frontend, specifically the HTTP client layer (`web/service/refresh-token.ts` and `web/service/fetch.ts`). This protects all API calls made by all implementations.

The Insight (Rule of Thumb)

  • Action 1: Use `globalThis.fetch()` (not `baseFetch()`) for the token refresh request to avoid recursive 401 interception.
  • Action 2: Use `localStorage` with a `is_other_tab_refreshing` key to coordinate refresh across browser tabs.
  • Action 3: Always clear the localStorage lock on `beforeunload` to prevent permanent cross-tab deadlocks.
  • Value: Prevents cascading authentication failures that could render the entire application unusable.
  • Trade-off: 1-second polling interval during cross-tab wait adds slight latency to token recovery.

Reasoning

The `baseFetch()` function includes a 401 interceptor that triggers token refresh. If this same function is used to perform the refresh, a 401 from the refresh endpoint triggers another refresh, creating an infinite loop that exhausts browser resources and generates hundreds of failed requests per second. Using raw `fetch()` bypasses this interceptor entirely.

The localStorage coordination prevents a scenario where 5 open tabs each independently detect a 401, each sends a refresh request, and 4 of those fail because the token was already rotated by the first tab. Instead, only one tab refreshes while others poll localStorage waiting for completion.

Code Evidence

From `web/service/refresh-token.ts:43-47`:

// Do not use baseFetch to refresh tokens.
// If a 401 response occurs and baseFetch itself attempts to refresh the token,
// it can lead to an infinite loop if the refresh attempt also returns 401.
const [error, ret] = await fetchWithRetry(globalThis.fetch(
  `${API_PREFIX}/refresh-token`, { method: 'POST', credentials: 'include' }
))

From `web/service/refresh-token.ts:4-21`:

const LOCAL_STORAGE_KEY = 'is_other_tab_refreshing'

function waitUntilTokenRefreshed() {
  return new Promise((resolve) => {
    const interval = setInterval(() => {
      if (localStorage.getItem(LOCAL_STORAGE_KEY) !== '1') {
        clearInterval(interval)
        resolve(undefined)
      }
    }, 1000)
  })
}

// Release lock on page unload to prevent permanent deadlocks
window.addEventListener('beforeunload', () => {
  localStorage.removeItem(LOCAL_STORAGE_KEY)
})

Related Pages

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment