# Security (/docs/security)



## What it protects against [#what-it-protects-against]

| Threat                             | Protection                                                                                                                                                                                                                              |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Token theft through XSS**        | Worker isolation plus `"memory"` storage: the token has no variable or storage key page code can reach                                                                                                                                  |
| **Token sent to the wrong server** | The token and CSRF header only go to the `baseUrl` origin, the page's origin and `authOrigins`. Each URL is parsed once, and that exact URL is both checked and fetched, so tricks like `//evil.com` or `\\evil.com` get no credentials |
| **Token read by page code**        | `getAccessToken()` refuses unless `exposeTokens: true`. Responses leaving the worker have the session's tokens removed                                                                                                                  |
| **Path injection**                 | `addTemplateToUrl` and `addToUrl` values are encoded as one path segment                                                                                                                                                                |
| **CSRF**                           | The double-submit header in cookie mode, if your server checks it                                                                                                                                                                       |
| **Tokens in logs**                 | Never in `LogEntry`, `AuthState` or messages between tabs                                                                                                                                                                               |
| **Refresh token reuse**            | One refresh at a time, per tab and across tabs                                                                                                                                                                                          |
| **A malicious dependency**         | There are none: zero runtime dependencies                                                                                                                                                                                               |

It doesn't address man-in-the-middle attacks: use HTTPS.

## What it can't do: XSS can still use the session [#what-it-cant-do-xss-can-still-use-the-session]

Worker isolation stops the token from being **stolen**. It can't stop injected script from **using** your client: `api.post("/transfer", …)` from an attacker gets the token attached like any other call.

That still changes a lot:

| Without isolation                              | With isolation                 |
| ---------------------------------------------- | ------------------------------ |
| The token is sent to the attacker's server     | The token stays in the worker  |
| The attack continues after the tab closes      | The attack ends with the page  |
| A stolen refresh token gives indefinite access | The refresh token never leaves |

It's defence in depth. Preventing XSS (a strict CSP, output encoding, careful dependencies) still comes first.

Endpoints of yours that hand out a new credential (an OAuth token exchange, an API key endpoint, a socket ticket) return it to whoever calls them, including injected script. Keep those credentials short-lived and narrowly scoped.

## Storage, from safest to least safe [#storage-from-safest-to-least-safe]

1. `authMode: "cookie"` with httpOnly cookies: the client never sees a token.
2. Header mode, `storage: "memory"`, worker on: the token can't be reached from the page.
3. Header mode, `"memory"`, worker off: the token sits in a closure on the page.
4. `"session"`: readable by any script on the page.
5. `"local"`: readable, and persists.
6. `"cookie"` (not httpOnly): readable, and sent with every request to your site.

The trade-off is reloads: `"memory"` logs the user out on reload, cookie mode doesn't.

## Recommended setups [#recommended-setups]

**You control the backend and share a site with it.** The strongest option:

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

```http
Set-Cookie: session=…; HttpOnly; Secure; SameSite=Strict; Path=/
Set-Cookie: csrftoken=…; Secure; SameSite=Strict; Path=/
```

**A SPA calling someone else's API.** Header mode with the defaults:

```ts
createClient({ baseUrl: "https://api.vendor.com", onAuthFailure: () => router.push("/login") });
```

**Sessions must survive reloads and cookies aren't an option.** Accept the XSS exposure and add a strict CSP. The worker is still worth keeping, since the live token stays inside it:

```ts
createClient({ baseUrl, storage: "local" });
```

## Content Security Policy [#content-security-policy]

```http
Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  worker-src 'self' blob:;
  connect-src 'self' https://api.example.com;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';
```

* `worker-src blob:` is required for the worker. Without it the client falls back to the main thread without an error.
* `connect-src` limits where `fetch` can go. It's what actually stops data being sent to an attacker's domain.
* Avoid `'unsafe-inline'` and `'unsafe-eval'` in `script-src`: they're what make XSS easy.

## Checklist [#checklist]

* `api.isWorker` is `true` in production browsers, or `worker: false` is deliberate
* `storage` is `"memory"`, or you've accepted the exposure
* Cookie mode has CSRF configured, and the server enforces it
* The CSP has `worker-src blob:` and a tight `connect-src`
* `baseUrl` is HTTPS in production
* `onAuthFailure` clears cached user data, not just the route
* Access tokens are short-lived (15 minutes or less), and refresh tokens rotate
* No token appears in analytics or error reports

Report vulnerabilities through a [security advisory](https://github.com/mohammadreza-zr/api-client/security/advisories/new), not a public issue.
