# Authenticated requests (https://session-kit.pages.dev/docs/client/requests)

`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](https://session-kit.pages.dev/docs/client/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:

```ts title="src/add-to-cart.ts"
import { authFetch } from "./api.js";

export async function addToCart(userId: number, productId: number) {
  const send = () =>
    authFetch("auth/carts/add", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        userId,
        products: [{ id: productId, quantity: 1 }],
      }),
    });

  let response = await send();
  // The 401 refreshed the token, so one more try uses the new one.
  if (response.status === 401) response = await send();
  if (!response.ok) throw new Error(`Add to cart failed (${response.status})`);
}
```

Two more options change the defaults:

```ts title="src/public-api.ts"
import { createAuthFetch } from "@monarcode/session-kit/http";

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

export const publicFetch = createAuthFetch(auth, {
  baseUrl: `${API}/`,
  // One `fetch` for public and private endpoints: signed out, requests are
  // sent without a token instead of rejecting.
  optional: 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.
  signOutOnRepeated401: 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.