Skip to content

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.

See Authentication 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.

OAuth CSRF protection

The Google OAuth flow signs a state parameter (JWT, FS_JWT_SECRET_KEY) and validates it on callback. See Google OAuth.

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. 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:

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:

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.

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).