# Token Refresh (/docs/token-refresh)



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:

<RefreshDemo />

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 [#the-refresh-request]

By default the client sends:

```http
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 [#custom-token-shapes]

If your server uses other field names, describe them:

```ts
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](/docs/worker-and-tabs) off. Prefer the object forms above.

## When refresh fails [#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 [#manual-refresh-and-long-uploads]

```ts
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`.
