# Security

FastSvelte ships sensible security defaults so you start on solid ground.

## Passwords

Passwords are hashed with **Argon2id** (`backend/app/util/hash_util.py`), a modern memory-hard algorithm. Plaintext passwords are never stored.

## Sessions

- Tokens are 256-bit random values (`secrets.token_urlsafe(32)`).
- Only a **SHA-256 hash** of the token is stored server-side — a database leak doesn't expose usable session tokens.
- The cookie is **HttpOnly** (JavaScript can't read it), **Secure** outside `dev`, and **SameSite** `strict` outside `dev` (`lax` in dev).
- Logout invalidates the session server-side; expired sessions are pruned by the [cron job](https://docs.fastsvelte.dev/reference/configuration/#background-jobs-cron).

See [Authentication](https://docs.fastsvelte.dev/features/authentication/index.md) for the full model.

## Access control

Precedence-based roles (`readonly` < `member` < `org_admin` < `sys_admin`) gate every route via `min_role_required(...)`. All business data is organization-scoped, so tenants are isolated — see [Multi-Tenancy](https://docs.fastsvelte.dev/features/multi-tenancy/index.md).

## OAuth CSRF protection

The Google OAuth flow signs a `state` parameter (JWT, `FS_JWT_SECRET_KEY`) and validates it on callback. See [Google OAuth](https://docs.fastsvelte.dev/features/google-oauth/index.md).

## AI spend protection

AI usage is **hard-capped by default**: when an organization exhausts its allotment + credits, calls are blocked rather than silently billed. Overage requires turning on *both* an org setting and a system-level kill switch, neither enabled by default — see [AI Usage & Credit Billing](https://docs.fastsvelte.dev/features/ai-billing/#overage-settings). This protects you from runaway model spend.

## CORS & email verification

Allowed origins are configured per environment in `backend/app/config/settings.py` and tighten in production. Accounts must verify their email before they can log in.

## Rate limiting

The abuse-prone public endpoints are rate limited per client IP, so one source can't hammer them:

| Endpoint                                     | Limit      |
| -------------------------------------------- | ---------- |
| `POST /auth/login`                           | 5 / 15 min |
| `POST /auth/signup`, `POST /auth/signup-org` | 3 / hour   |
| `POST /password/forgot`                      | 3 / hour   |
| `POST /auth/resend-verification`             | 10 / min   |

The priority is the endpoints that **send email to a user-supplied address** (signup, forgot-password, resend-verification): left open they let an attacker burn your email spend and sender reputation regardless of how much traffic you have. Login is capped against credential stuffing. A breach returns the standard `ErrorResponse` with a `Retry-After` header.

To rate limit another route, add the dependency to it:

```python
from fastapi import Depends
from app.util.rate_limit import rate_limit

@router.post("/expensive", dependencies=[Depends(rate_limit("10/minute"))])
```

### Storage (read before scaling)

Counters live in **process memory by default** (`FS_RATE_LIMIT_STORAGE_URI=async+memory://`). The shipped container runs a single process, so this is correct for **one instance**. But each instance keeps its own counters, so if you run **more than one instance** (horizontal scaling / replicas behind a load balancer) the effective limit multiplies by the number of instances. For a real limit across instances, point it at a shared store:

```text
FS_RATE_LIMIT_STORAGE_URI=async+redis://your-redis-host:6379/0
```

Async Redis needs the `coredis` package (`uv add coredis`).

### Behind a proxy

The limiter reads the client IP from `X-Forwarded-For`, which hosting platforms set for you. If you'd rather rate limit at the edge, a reverse proxy like nginx can do it too. See its [`limit_req` docs](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html).

### Deliberately left out

Kept out to stay lean; add if your threat model calls for it:

- **Per-account login limiting** (keying login on the target email, not just the IP) defends a distributed attack against one account. Worth adding once you have accounts worth attacking.
- **The AI endpoint** is authenticated, so it's not a public abuse surface, and it's already hard-capped by the AI spend protection above. It gets no separate HTTP limit.
- **Reset and verify token endpoints** rely on high-entropy tokens rather than a request limit.

## Not included (add as needed)

To stay lean, FastSvelte does **not** bundle security-header middleware (CSP/HSTS). Add it when your threat model calls for it, e.g. a middleware that sets security headers.

## Production checklist

- Strong, unique `FS_JWT_SECRET_KEY` and `FS_CRON_SECRET`.
- Serve over HTTPS so `Secure` cookies take effect.
- Strict `FS_CORS_ORIGINS` for production.
- Stripe live keys + verified webhook secret (see [Billing & Subscriptions](https://docs.fastsvelte.dev/features/billing/index.md)).
