Skip to content

Authenticated Fetch

auth.fetch() is a drop-in replacement for the native fetch(). It adds authentication automatically and retries on 401.

const res = await auth.fetch('/api/profile');
const data = await res.json();
const res = await auth.fetch('/api/profile');
const data = await res.json();

It has the same signature as fetch() — same RequestInit options, same Response return. Headers, streaming, AbortController — everything works.

  1. Sends the request with credentials (cookie mode) or Authorization header (bearer mode)
  2. If the response is 401, refreshes the access token
  3. Retries the original request once with the new token
  4. If the retry also fails, returns the 401 response

Multiple concurrent 401s share a single refresh call — no thundering herd.

// JSON POST
const res = await auth.fetch('/api/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'test' }),
});

// Streaming
const stream = await auth.fetch('/api/stream');
const reader = stream.body.getReader();

// AbortController
const controller = new AbortController();
await auth.fetch('/api/slow', { signal: controller.signal });
// JSON POST
const res = await auth.fetch('/api/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'test' }),
});

// Streaming
const stream = await auth.fetch('/api/stream');
const reader = stream.body.getReader();

// AbortController
const controller = new AbortController();
await auth.fetch('/api/slow', { signal: controller.signal });

For use with custom HTTP clients (Axios, TanStack Query, etc.) in bearer mode:

const token = await auth.getToken();
// Returns the current access token, or null in cookie mode
const token = await auth.getToken();
// Returns the current access token, or null in cookie mode

getToken() refreshes automatically if the token is expired. Concurrent calls share a single refresh.

// Cookie mode — just enable credentials
axios.defaults.withCredentials = true;
// Cookie mode — just enable credentials
axios.defaults.withCredentials = true;
// Bearer mode — attach token via interceptor
axios.interceptors.request.use(async (config) => {
  const token = await auth.getToken();
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});
// Bearer mode — attach token via interceptor
axios.interceptors.request.use(async (config) => {
  const token = await auth.getToken();
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});
const { data } = useQuery({
  queryKey: ['profile'],
  queryFn: async () => {
    const token = await auth.getToken();
    const res = await fetch('/api/profile', {
      headers: { Authorization: `Bearer ${token}` },
    });
    return res.json();
  },
});
const { data } = useQuery({
  queryKey: ['profile'],
  queryFn: async () => {
    const token = await auth.getToken();
    const res = await fetch('/api/profile', {
      headers: { Authorization: `Bearer ${token}` },
    });
    return res.json();
  },
});
Cookie ModeBearer Mode
auth.fetch() addscredentials: 'include'Authorization: Bearer <token>
getToken() returnsnull (tokens are in cookies)The access token string
Who stores tokensBrowser (HttpOnly cookies)You (via TokenStorage)