@mrzr/api-client

Token Refresh

How @mrzr/api-client refreshes access tokens automatically, sends one refresh for many concurrent 401s, and how to map custom token response shapes.

You don't write refresh logic. The client refreshes in two situations:

  1. Before expiry. If the access token is a JWT that expires within refreshSkewMs (default 30 s), the client refreshes it before sending the next request.
  2. After a 401. If the server answers 401, the client refreshes and retries the request once with the new token.

Either way, requests that arrive during a refresh wait for it. Many 401s at once produce one refresh request, not one per request:

One refresh for many requests

Expire the token, then send 8 requests at the same time. Open DevTools → Network to watch it happen.

  1. 1.The access token is replaced with an expired one.
  2. 2.8 requests go out at once. The server answers each with 401.
  3. 3.The client pauses them and sends a single refresh request.
  4. 4.All 8 requests are retried with the new token and succeed.
GET #1 not sentGET #2 not sentGET #3 not sentGET #4 not sentGET #5 not sentGET #6 not sentGET #7 not sentGET #8 not sent

Refresh requests sent: –

Tabs that share a session take turns refreshing through the Web Locks API, so a rotating refresh token is never spent twice.

The refresh request

By default the client sends:

POST /auth/refresh
Content-Type: application/json

{ "refresh": "<refresh token>" }

and reads the new tokens from the response the same way it reads the login response.

Custom token shapes

If your server uses other field names, describe them:

createClient({
  // response: { result: { jwt: "…", renew: "…", expires_in: 900 } }
  extractTokens: {
    accessKeys: ["jwt"],
    refreshKeys: ["renew"],
    expiresInKeys: ["expires_in"],
    roots: ["result"],
  },
  // request body: { "refresh_token": "…" }
  buildRefreshBody: { field: "refresh_token" },
});

Both options also accept a function, but a function can't be sent into the Web Worker, so passing one turns worker isolation off. Prefer the object forms above.

When refresh fails

Refresh endpoint answersResult
401 or 403The session is cleared and onAuthFailure fires. Tabs sharing the session ("local" or "cookie" storage) are logged out too
5xx, timeout, network errorThe session is kept. The original request fails, and the next one tries again

A server hiccup never logs the user out.

Manual refresh and long uploads

const ok = await api.refresh();   // true when the session is usable again

// A 10-minute upload: refresh first if the token dies within 10 minutes.
await api.post("/upload", file, { uploadSkewMs: 10 * 60_000 });

To skip the 401 → refresh → retry flow for one call, pass refreshTokenCheck: false.

On this page