# Authentication (/docs/authentication)



The client owns the tokens. You never set an `Authorization` header or read storage yourself.

## Two modes [#two-modes]

|                              | `authMode: "header"` (default)  | `authMode: "cookie"`               |
| ---------------------------- | ------------------------------- | ---------------------------------- |
| How the credential travels   | `Authorization: Bearer <token>` | httpOnly cookie set by your server |
| Can page JavaScript read it? | No, in worker mode              | No                                 |
| Needs CSRF protection?       | No                              | Yes                                |
| Good for                     | SPAs and APIs on another domain | Apps where you control the backend |

## Header mode [#header-mode]

```ts
const api = createClient({ baseUrl: "https://api.example.com" });

await api.login({ email, password });   // tokens captured from the response
await api.get("/me");                   // Authorization: Bearer … attached
await api.logout();
```

The token is only ever sent to the `baseUrl` origin and the page's own origin. To send it to another API of yours, list that origin:

```ts
createClient({ baseUrl, authOrigins: ["https://files.example.com"] });
```

Already have tokens, for example from an OAuth callback or SSR? Seed them with `api.setTokens({ accessToken, refreshToken })`.

### Where tokens are stored [#where-tokens-are-stored]

| `storage`            | Survives reload                     | Readable by page JS                   |
| -------------------- | ----------------------------------- | ------------------------------------- |
| `"memory"` (default) | No                                  | No: the token never leaves the worker |
| `"session"`          | Same tab only                       | Yes                                   |
| `"local"`            | Yes                                 | Yes                                   |
| `"cookie"`           | Yes (readable cookie, not httpOnly) | Yes                                   |

`"memory"` is the safest option. The user logs in again after a reload, unless the refresh token survives in an httpOnly cookie on your server. You can also pass your own adapter: any object with `get`, `set` and `clear`.

## Cookie mode [#cookie-mode]

Your server sets `HttpOnly; Secure` cookies at login. The client never sees a token. It just sends requests with `credentials: "include"`.

```ts
const api = createClient({
  baseUrl: "https://api.example.com",
  authMode: "cookie",
  xsrfCookieName: "csrftoken",   // see CSRF below
});
```

JavaScript can't see httpOnly cookies, so after a page reload the client doesn't know whether a session exists. Ask the server once on startup:

```ts
const { isAuthenticated, user } = await api.restoreSession("/auth/me");
```

Without a URL, `restoreSession()` tries the refresh endpoint instead.

### CSRF [#csrf]

With cookies, a malicious site can make the browser send your cookie along with a forged request. The standard defence is the double-submit pattern: the server sets a readable CSRF cookie, and the client copies its value into a header.

```ts
createClient({ authMode: "cookie", xsrfCookieName: "csrftoken" });
// POST/PUT/PATCH/DELETE and refresh → X-CSRF-Token: <cookie value>
```

If the token isn't in a cookie (for example it's in a `<meta>` tag), use `getCsrfToken: () => …` instead. Your server must still compare the header with the cookie and reject mismatches.

## Auth state [#auth-state]

```ts
const state = await api.getAuthState();   // { isAuthenticated, expiresAt, user }
const off = api.onAuthStateChange((state) => { /* … */ });
```

The state never contains tokens. `onAuthFailure` in the client options fires when the session is gone for good: the refresh endpoint rejected it (401/403), or the user logged out in any tab.
