Skip to Content
Using the client

Other HTTP clients

Send the access token with ky, axios, openapi-fetch or any other HTTP client.

authFetch takes a URL or a Request as well as a path, as fetch does. An HTTP client that lets you choose its fetch can send its requests through it, and they get everything authFetch does: the token stays on your API's origin, a 401 refreshes once, and only a GET or HEAD is sent again.

A client with its own transport uses the calls authFetch is built on instead.

Clients that take a fetch

With ky, pass authFetch as fetch:

src/ky.ts
import  from "ky";

import {  } from "./api.js";
import {  } from "./backend.js";

export const  = .({ : `${}/`, :  });

export async function (: number): <unknown> {
  return .(`auth/carts/user/${}`).();
}

With axios, choose its fetch adapter and pass authFetch in env:

src/axios.ts
import  from "axios";

import {  } from "./api.js";
import {  } from "./backend.js";

export const  = .({
  : ,
  : "fetch",
  : { :  },
});

openapi-fetch takes it the same way as ky:

const client = createClient<paths>({ baseUrl: API, fetch: authFetch });

What changes for the client's calls:

  • Give the client the same API. Its base URL must be on the origin that authFetch was created for. A request for another origin rejects with INVALID_OPTIONS, and the token isn't sent.
  • Signed out, every call rejects with UNAUTHENTICATED. If the client also calls public endpoints, such as your sign-in, create authFetch with optional: true. Those requests then go without a token.
  • A 401 reaches the client after the refresh. For a GET or HEAD, the client sees the answer to the request that was sent again. For a write, it sees the 401, as ky's HTTPError or axios's AxiosError. Retry it where sending it twice is safe.
  • Session kit's errors keep their code. ky rejects with the AuthError itself. axios wraps it in an AxiosError that has the same code, with the AuthError as its cause. Match on error.code, which works with both, rather than on instanceof AuthError.
  • authFetch sets Authorization. While signed in, it replaces a header the client set itself.
  • Only what a Request holds is sent. ky hands fetch the options it doesn't know itself, such as Next.js's next. authFetch doesn't pass those on.

Clients without a fetch option

createAuthFetch and the effetch plugin are built on three calls, and a client with its own transport can use the same ones:

  1. Before a request, auth.credentials.get() resolves the credentials to send, waiting for a restore or a refresh that is under way. It resolves null when nobody is signed in.
  2. After a 401, auth.credentials.renew(credentials) takes the credentials that request was sent with, and resolves new ones. However many requests failed, they share one refresh. It resolves null when the user was signed out instead.
  3. After a second 401, auth.credentials.reject(credentials) ends the session, unless newer credentials have replaced the ones you pass.

An axios instance that keeps its default adapter does it with two interceptors:

src/axios-interceptors.ts
import type {  } from "@monarcode/session-kit";
import , { type  } from "axios";

import {  } from "./auth.js";
import {  } from "./backend.js";

type  =  & { ?: boolean };

// One instance for one API, so the token goes nowhere else.
export const  = .({ :  });

/** The credentials each request was sent with. */
const  = new <, >();

...(async () => {
  const  = await ..();
  // Signed out, the request goes without a token, as a public one would.
  if () {
    ..("Authorization", `Bearer ${.}`);
    .(, );
  }
  return ;
});

...(, async (: unknown) => {
  if (!.() || .?. !== 401) throw ;
  const :  | undefined = .;
  const  =  && .();
  if (! || !) throw ;

  if (.) {
    // The new token was refused too: end the session.
    await ..();
    throw ;
  }
  const  = await ..();
  // As with authFetch, only a GET or HEAD is sent twice. For anything else
  // the caller decides.
  if (! || !["get", "head"].(. ?? "")) throw ;
  const :  = { ..., : true };
  return .();
});

Three things createAuthFetch does for you are yours to keep here:

  • Keep the token on one origin. Give the client one base URL, and don't pass it absolute URLs to other hosts.
  • Don't read the token early. Ask for credentials in the request hook, never once at startup. A token saved in a variable goes stale at the next refresh.
  • Check the account after slow work. auth.isCurrent(credentials) tells whether the session that sent a request is still the signed-in one, before you show its answer.

On this page