diff --git a/docs/packages/http.md b/docs/packages/http.md index c8d66d3..5961cd8 100644 --- a/docs/packages/http.md +++ b/docs/packages/http.md @@ -99,6 +99,29 @@ The default is also exported as a barrel-level constant for consumers that want import {DEFAULT_TIMEOUT_MS} from '@script-development/fs-http'; ``` +## Authentication & XSRF + +`withXSRFToken` defaults to `false` because the factory does not know what authentication shape it sits in front of — Laravel Sanctum SPA, stateless API tokens, OIDC backends, and third-party API gateways all want different answers. Consumers must opt in explicitly when their backend plants an XSRF cookie. + +### Laravel Sanctum SPA + +Laravel's Sanctum stateful middleware plants an `XSRF-TOKEN` cookie on the SPA's domain during the `/sanctum/csrf-cookie` handshake. axios 1.x will only read that cookie and forward it as the `X-XSRF-TOKEN` header when `withXSRFToken: true` is passed explicitly. Without that flag every state-changing request (POST / PUT / PATCH / DELETE) returns **HTTP 419 (CSRF token mismatch)** from Sanctum's middleware. + +```typescript +const http = createHttpService(`${location.origin}/api`, { + withXSRFToken: true, // Laravel Sanctum SPA — read XSRF-TOKEN cookie + withCredentials: true, // send session cookie (default true) +}); +``` + +::: warning Mocked transports hide this failure mode +Page-integration test suites that mock `@script-development/fs-http` (per ADR-0017) bypass axios entirely — the XSRF cookie / `X-XSRF-TOKEN` header round-trip never executes, so a missing `withXSRFToken: true` does not surface in test output. The first signal arrives in production: every state-changing request to a Sanctum SPA backend returns 419. Set `withXSRFToken: true` at instantiation in any Sanctum SPA consumer. +::: + +### Stateless / token / non-Sanctum stacks + +Stateless API token stacks (Bearer tokens, OAuth2 access tokens), OIDC backends that do not plant an `XSRF-TOKEN` cookie, and third-party API gateways should leave `withXSRFToken` at the default `false`. Enabling it is a no-op when no `XSRF-TOKEN` cookie exists on the request origin, but the explicit `false` documents the consumer's authentication shape and prevents drift if a Sanctum-shaped middleware is added to the same domain later. + ## Middleware The middleware system lets you intercept requests at three points in the lifecycle. Every registration returns an unregister function: @@ -199,14 +222,14 @@ try { ### `createHttpService(baseURL, options?)` -| Parameter | Type | Description | -| -------------------------- | ------------------------ | ----------------------------------------------------------------------- | -| `baseURL` | `string` | Base URL for all requests | -| `options.timeout` | `number \| undefined` | Request timeout in milliseconds (default: `30000`; pass `0` to disable) | -| `options.headers` | `Record` | Default headers | -| `options.withCredentials` | `boolean` | Send cookies cross-origin (default: `true`) | -| `options.withXSRFToken` | `boolean` | Include XSRF token (default: `false`) | -| `options.smartCredentials` | `boolean` | Auto-toggle credentials by origin (default: `false`) | +| Parameter | Type | Description | +| -------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `baseURL` | `string` | Base URL for all requests | +| `options.timeout` | `number \| undefined` | Request timeout in milliseconds (default: `30000`; pass `0` to disable) | +| `options.headers` | `Record` | Default headers | +| `options.withCredentials` | `boolean` | Send cookies cross-origin (default: `true`) | +| `options.withXSRFToken` | `boolean` | Forward `XSRF-TOKEN` cookie as `X-XSRF-TOKEN` header (default: `false`). Set `true` for Laravel Sanctum SPA; leave `false` for stateless / token / non-Sanctum stacks. See [Authentication & XSRF](#authentication-xsrf). | +| `options.smartCredentials` | `boolean` | Auto-toggle credentials by origin (default: `false`) | ### Constants diff --git a/packages/http/README.md b/packages/http/README.md index de934cb..41e7f66 100644 --- a/packages/http/README.md +++ b/packages/http/README.md @@ -46,6 +46,10 @@ Creates a new HTTP service instance. Per **Doctrine #8 library-author extension** (war-room CLAUDE.md, 2026-04-22), the factory applies a **30000ms default timeout** with `timeout: 0` opt-out and per-request override. See [the docs site Timeout section](https://packages.script.nl/packages/http#timeout) for the full surface contract. +### Authentication & XSRF + +For Laravel Sanctum SPA consumers, `withXSRFToken: true` is required to avoid HTTP 419 (CSRF mismatch) on state-changing requests; mocked transports do not surface this. See [the docs site Authentication & XSRF section](https://packages.script.nl/packages/http#authentication-xsrf) for the full discussion (including stateless / non-Sanctum guidance). + ### Request Methods - `getRequest(endpoint, options?)` — GET request