Skip to content

Commit 2075b90

Browse files
Merge pull request #75 from script-development/armorer/queue-22-xsrf-sanctum-docs
docs(http): Sanctum XSRF gotcha — closes queue #22 docs path
2 parents 36abd7e + 2661e37 commit 2075b90

2 files changed

Lines changed: 35 additions & 8 deletions

File tree

docs/packages/http.md

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,29 @@ The default is also exported as a barrel-level constant for consumers that want
9999
import {DEFAULT_TIMEOUT_MS} from '@script-development/fs-http';
100100
```
101101

102+
## Authentication & XSRF
103+
104+
`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.
105+
106+
### Laravel Sanctum SPA
107+
108+
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.
109+
110+
```typescript
111+
const http = createHttpService(`${location.origin}/api`, {
112+
withXSRFToken: true, // Laravel Sanctum SPA — read XSRF-TOKEN cookie
113+
withCredentials: true, // send session cookie (default true)
114+
});
115+
```
116+
117+
::: warning Mocked transports hide this failure mode
118+
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.
119+
:::
120+
121+
### Stateless / token / non-Sanctum stacks
122+
123+
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.
124+
102125
## Middleware
103126

104127
The middleware system lets you intercept requests at three points in the lifecycle. Every registration returns an unregister function:
@@ -199,14 +222,14 @@ try {
199222

200223
### `createHttpService(baseURL, options?)`
201224

202-
| Parameter | Type | Description |
203-
| -------------------------- | ------------------------ | ----------------------------------------------------------------------- |
204-
| `baseURL` | `string` | Base URL for all requests |
205-
| `options.timeout` | `number \| undefined` | Request timeout in milliseconds (default: `30000`; pass `0` to disable) |
206-
| `options.headers` | `Record<string, string>` | Default headers |
207-
| `options.withCredentials` | `boolean` | Send cookies cross-origin (default: `true`) |
208-
| `options.withXSRFToken` | `boolean` | Include XSRF token (default: `false`) |
209-
| `options.smartCredentials` | `boolean` | Auto-toggle credentials by origin (default: `false`) |
225+
| Parameter | Type | Description |
226+
| -------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
227+
| `baseURL` | `string` | Base URL for all requests |
228+
| `options.timeout` | `number \| undefined` | Request timeout in milliseconds (default: `30000`; pass `0` to disable) |
229+
| `options.headers` | `Record<string, string>` | Default headers |
230+
| `options.withCredentials` | `boolean` | Send cookies cross-origin (default: `true`) |
231+
| `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). |
232+
| `options.smartCredentials` | `boolean` | Auto-toggle credentials by origin (default: `false`) |
210233

211234
### Constants
212235

packages/http/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Creates a new HTTP service instance.
4646

4747
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.
4848

49+
### Authentication & XSRF
50+
51+
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).
52+
4953
### Request Methods
5054

5155
- `getRequest<T>(endpoint, options?)` — GET request

0 commit comments

Comments
 (0)