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:
- 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. - 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.The access token is replaced with an expired one.
- 2.8 requests go out at once. The server answers each with 401.
- 3.The client pauses them and sends a single refresh request.
- 4.All 8 requests are retried with the new token and succeed.
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 answers | Result |
|---|---|
| 401 or 403 | The session is cleared and onAuthFailure fires. Tabs sharing the session ("local" or "cookie" storage) are logged out too |
| 5xx, timeout, network error | The 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.
Authentication
Log users in with bearer tokens or httpOnly cookies. Choose where tokens are stored, restore a cookie session on page load and enable CSRF protection.
Cancellation
Cancel in-flight requests when the user leaves a page, closes a modal or types the next search keystroke. Cancel by key, group, URL pattern or scope.