Authenticated requests
A fetch that sends the access token to one API, refreshes on a 401, and never leaks the token.
createAuthFetch(auth, { baseUrl }) returns a fetch for one API. It's
built so that a token can't leak and a user can't be signed out by accident:
| When… | authFetch… |
|---|---|
| The request is for another origin, or has a username or password in its URL | Rejects with INVALID_OPTIONS. The token goes only to baseUrl's origin. |
| The response is a redirect | Rejects rather than following it. |
| The response is a 401 | Refreshes once, then sends a GET or HEAD again with the new token. |
| Any other method gets a 401 | Refreshes, and resolves the 401. It never sends a write twice on its own. |
| The replayed request gets a 401 too | Signs out, unless signOutOnRepeated401 is false. |
| The session changes during the request | Rejects with SESSION_CHANGED rather than resolve another account's answer. |
| Nobody is signed in | Rejects with UNAUTHENTICATED, unless optional is true. |
| The session is locked | Rejects with LOCKED, even when optional is true. |
| A refresh fails | Rejects with that failure, such as REFRESH_FAILED. |
| The network fails | Rejects as fetch does, unchanged. |
Call it with a path, which is resolved against baseUrl as a link is
against a page's address. End baseUrl with /, so that
authFetch("orders") lands under it. It also takes a URL or a Request,
as fetch does, so an HTTP client with a fetch option can send its
requests through it. See Other HTTP clients.
A Request with a body can be sent once, as with fetch. To retry a write
after a 401, send a clone you kept.
Because a write is never replayed for you, retry it yourself where sending it twice is safe. By then the token has been refreshed:
import { } from "./api.js";
export async function (: number, : number) {
const = () =>
("auth/carts/add", {
: "POST",
: { "Content-Type": "application/json" },
: .({
,
: [{ : , : 1 }],
}),
});
let = await ();
// The 401 refreshed the token, so one more try uses the new one.
if (. === 401) = await ();
if (!.) throw new (`Add to cart failed (${.})`);
}Two more options change the defaults:
import { } from "@monarcode/session-kit/http";
import { } from "./auth.js";
import { } from "./backend.js";
export const = (, {
: `${}/`,
// One `fetch` for public and private endpoints: signed out, requests are
// sent without a token instead of rejecting.
: true,
// For APIs that answer 401 for reasons other than a rejected token, so a
// second 401 from one of those endpoints doesn't sign users out.
: false,
});Without a refresh token or a refresh option, there's nothing to refresh
with, so the first 401 signs the user out, whatever signOutOnRepeated401
is.