Skip to Content
Using the client

Other HTTP clients

Attach the access token with axios, ky, or any other HTTP client, using the client's credentials calls.

createAuthFetch and the effetch plugin are built on three calls, and any other HTTP client 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 a newer one has replaced it.

With axios, that's two interceptors:

src/axios.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 .();
});

With ky, two hooks do the same. The request's context carries the credentials from one hook to the other:

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

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

export const  = .({
  : `${}/`,
  : {
    : [
      async ({ ,  }) => {
        const  = await ..();
        if (!) return;
        ..credentials = ;
        ..(
          "Authorization",
          `Bearer ${.}`,
        );
      },
    ],
    : [
      async ({ , ,  }) => {
        const  = ..credentials as  | undefined;
        if (. !== 401 || !) return;

        if (..replayed) {
          // The new token was refused too: end the session.
          await ..();
          return;
        }
        const  = await ..();
        // As with authFetch, only a GET or HEAD is sent twice.
        if (! || !["GET", "HEAD"].(.)) return;
        ..credentials = ;
        ..replayed = true;
        const  = new (.);
        .("Authorization", `Bearer ${.}`);
        return .({ : new (, {  }) });
      },
    ],
  },
});

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.