HTTP Adapters
Swap fetch for axios — or any HTTP transport.
The client's HTTP layer is pluggable. Default is fetch; axios ships as an option.
The contract
FetchAdapter (default)
Uses the global fetch — works in all modern browsers, Node 18+, React Native, Cloudflare Workers, Deno, Bun.
JSON request bodies are automatically JSON.stringify-ed; JSON responses are automatically parsed when Content-Type: application/json.
createAxiosAdapter(axiosInstance)
Wraps an axios instance so you keep the rest of your app's HTTP setup (interceptors, retries, baseURL) while letting the auth client share the transport.
Custom adapter
Wrap whatever client you like. As long as you satisfy the contract, the library doesn't care.
Sharing auth with your own HTTP client
createAxiosAdapter is for letting AuthClient use your axios instance for its own auth calls. The opposite direction — making your app's axios/fetch carry the auth token — is attachToAxios / attachToFetch.
These wire interceptors that attach getAuthHeaders() to every outgoing request and (by default) retry once on 401 after a refresh. They exist as both standalone helpers and instance methods on AuthClient.
attachToAxios returns an unsubscribe function — call it on logout/unmount to eject the interceptors. The same instance can be re-attached afterward.
Sharing one axios for both? It is safe to point
AuthClient'shttpAdapterat the same axios youattachToAxios: the interceptor automatically skipsAuthClient's own requests (createAxiosAdaptertags them) and never refresh-retries the auth endpoints (/auth/refresh-token,/auth/login,/auth/logout,/auth/logout-all), so an expired-session boot can't deadlock the refresh call. As a result,attachToAxios/attachToFetchnever attach the bearer to — or refresh-retry — those auth paths: do login/logout via theAuthClient/AccountManagermethods, and if you renamed your auth endpoints, pass the custom paths inskipPaths. Even so, the cleanest setup is two instances — a plain one asAuthClient's transport, and a separate app instance withattachToAxios+onRefreshFailedfor your API calls:
For fetch, the wrapper returns a new fetch-shaped function; cleanup is just "stop calling it":
In cookie mode both helpers also set withCredentials: true (axios) / credentials: 'include' (fetch) automatically, mirroring shouldSendCookies().
AttachOptions
Both helpers (and instance methods) take an optional third argument. It extends GetAuthHeadersOptions (so authHeaderName, skipAuthHeader, etc. are also valid) and adds:
| Option | Type | Default | Purpose |
|---|---|---|---|
retryOn401 | boolean | true | Refresh + retry the request once on a 401. Set false for third-party APIs that shouldn't share refresh semantics. |
skipPaths | Array<string | RegExp | ((url: string) => boolean)> | [] | URLs to skip — no auth header, no retry. String entries match as a URL suffix. |
onRefreshFailed | (error: unknown) => void | Promise<void> | no-op | Called when the in-interceptor refresh fails (e.g. the refresh token expired). React by redirecting to login, clearing state, etc. The original 401 still propagates. |
Attaching to an account manager
The first argument of attachToAxios / attachToFetch is anything matching AuthHeaderProvider:
AuthClient implements it — and so do the multi-account managers (AccountManager / CookieAccountManager), which delegate to whichever account is currently active. That means you can attach a single shared axios/fetch instance to the manager and it always sends the active account's bearer — with no re-attach when you switchAccount():
The same one-liner works on the manager instances directly (accounts.attachToFetch()) or via the standalone helpers (attachToAxios(accounts, api)). See Multi-account login & switching.
When no account is active. A manager with no active account resolves no headers, so requests would go out anonymous (and 401) — while an auth provider fed a separate bootstrap client still shows the user signed in. Avoid that split-brain by giving the manager a
fallbackClientand feedingresolveActiveClient()(notgetActiveClient()) to your provider, so both resolve the same client. PassonNoActiveAccountto be told when auth resolves to nothing instead of silently 401ing:
A one-shot
accounts.resolveActiveClient()read at render time is not enough: the persisted account index loads asynchronously, so an early read returns the fallback and never updates — re-introducing the very divergence this avoids.
Cookie mode and credentials
When the client is in cookie mode, every request goes out with credentials: 'include'. Make sure your CORS config has Access-Control-Allow-Credentials: true and an explicit origin allowlist (* is forbidden with credentials).