API Security & Token Hardening
Enterprise-grade API security architecture: 4-layer defense-in-depth, bearer tokens, tenant isolation, rate limiting, CSRF, and CSP.
On this page
SveltyCMS employs a zero-trust architecture for API access — authentication alone is insufficient. Every API request passes through 4 independent security layers that re-validate permissions at each boundary.
1. Defense-in-Depth Architecture
Request → Layer 1 (Middleware) → Layer 2 (Dispatcher) → Layer 3 (Handler) → Layer 4 (Page Action) → Response
| Layer | What It Checks | Failure Mode |
|---|---|---|
| Layer 1 — Middleware | CSP, CORS, HSTS, X-Frame-Options, firewall patterns, body size (10MB), rate limiting, __Host- cookie prefix, CSRF |
403/429 before any business logic runs |
| Layer 2 — Dispatcher | ENDPOINT_PERMISSIONS mapping, SCIM blocking, admin fast-path |
403 for any unmapped namespace |
| Layer 3 — Handler | Admin verification for mutations, media:write/media:delete checks |
403 on unauthorized mutations |
| Layer 4 — Page Action | requireCollectionBuilderPermission(), inline RBAC checks |
403 before state change |
Fail-Closed Default: If a permission mapping is missing or ambiguous, the dispatcher returns 403 — never 200 with leaked data.
2. Token Architecture
2.1 Website Tokens (API Keys)
Designed for machine-to-machine communication. Stateless secrets bound to a specific tenantId and role.
| Property | Detail |
|---|---|
| Generation | CSPRNG with rejection sampling (zero modulo bias) |
| Storage | SHA-256 hashed before persistence — plaintext never stored |
| Types | content-api (read/restricted), admin-api (full access) |
| Expiry | Configurable per-token with automatic invalidation |
| Consumption | Atomic one-time token support via dbAdapter.system.websiteTokens.consume() — prevents TOCTOU race conditions |
| Lookup | Single-pass getByToken — no redundant round-trips |
2.2 Session Tokens
Browser-based authentication uses secure session cookies:
- Cookie prefix:
__Host-in production (RFC 6265bis — prevents subdomain leakage) - Attributes:
httpOnly,secure,sameSite: strict - Rotation: Every 15 minutes for active users
- Cascading invalidation: Password change purges all sessions across all devices
- 3-layer cache: Memory (L1) → Redis (L2) → Database (L3)
3. Authentication Flow
3.1 Bearer Token Validation
1. Extract token from Authorization: Bearer <token> header
2. Single DB query: dbAdapter.system.websiteTokens.getByToken
3. Normalize legacy tokens → default to content-api
4. Create Virtual User context (no full User document hydration)
5. Verify tenantId match → reject mismatched tokens
locals.user = {
_id: `token:${token._id}`,
role: token.type === "admin-api" ? "admin" : "guest",
tenantId: token.tenantId ?? locals.tenantId,
isApiToken: true,
};
3.2 GraphQL Authentication
GraphQL requests pass through the same middleware pipeline as REST:
- HTTP entry:
handleRequest()rejects unauthenticated requests (401) before schema execution - Validation plugin: Query depth limit (8), alias limit (15)
- Introspection: Explicitly blocked in production via
NoSchemaIntrospectionCustomRule - Subscriptions: Context handler validates sessions before WebSocket upgrade
3.3 SCIM 2.0 Authentication
SCIM endpoints (/api/scim/v2/Users, /Groups, /Bulk) use Bearer token authentication with additional tenant-scoped RBAC checks:
- SCIM tokens require
system:adminor explicit SCIM provisioning permissions - Bulk operations are validated for cross-tenant leakage before execution
- PATCH operations use RFC 7644-compliant filter evaluation
4. Multi-Tenant Hardening
4.1 Orphaned Token Protection
If a token’s owner is deleted (tenantId: null), the system isolates the token to the current request’s tenant context — preventing cross-tenant privilege escalation.
4.2 Tenant Isolation Checks
Every Bearer-token request is verified against the environment’s tenantId. Mismatched tokens are rejected with 403 and an audit log event.
4.3 Batch Operation Guard
batch-module.ts rejects coalesced operations containing mixed tenant IDs — preventing cross-tenant data leakage in shared-DB deployments.
5. Rate Limiting & DoS Prevention
| Mechanism | Location | Detail |
|---|---|---|
| Login brute-force | handle-authentication.ts |
sveltekit-rate-limiter — 5 attempts → 15-min lockout (HTTP 423) |
| API rate limit | SecurityResponseService |
rate-limiter-flexible with Redis-backed state, per-IP and per-tenant |
| Body size limit | api-handler.ts |
10MB cap — returns 413 PAYLOAD_TOO_LARGE before parsing |
| Memory load shedding | hooks.server.ts |
Rejects mutation traffic (503) when heap > 90% |
| Retry-After header | RFC 6585 compliant | All 429 responses include Retry-After: 60 |
6. CSP & Security Headers
All API responses include mandatory security headers:
| Header | Value | Purpose |
|---|---|---|
Content-Security-Policy |
script-src 'self' (production API routes) |
Blocks inline script execution |
Strict-Transport-Security |
max-age=31536000; includeSubDomains |
Enforces HTTPS |
X-Frame-Options |
DENY |
Prevents clickjacking |
X-Content-Type-Options |
nosniff |
Prevents MIME sniffing |
Cross-Origin-Opener-Policy |
same-origin |
Spectre/Meltdown protection |
Cross-Origin-Embedder-Policy |
require-corp |
Cross-origin isolation |
Cross-Origin-Resource-Policy |
same-origin |
Resource isolation |
Referrer-Policy |
strict-origin-when-cross-origin |
Referrer leakage control |
GraphQL Playground CSP: The relaxed CSP (
unsafe-inline,unsafe-eval) is only applied in non-production environments. InNODE_ENV=production, GraphQL endpoints use the same strict CSP as REST APIs.
7. CSRF Protection
Multi-layer origin and referer validation for all mutation requests (POST, PUT, DELETE, PATCH):
- SvelteKit CSRF: Enabled by default for form actions
- Origin check: All mutations verify
OriginorRefererheaders match the current host - SAML CSRF: Dual-cookie protection for SAML ACS endpoints
- Token-bound mutations: Bearer token requests are CSRF-immune (no cookie dependency)
8. Performance Benchmarks
All metrics self-measured via bun test tests/benchmarks/ — reproducible on any machine.
| Metric | p95 | Adapter |
|---|---|---|
| Bearer token validation | 0.06 ms | SQLite (Local SDK) |
| Bearer token validation | 0.37 ms | MariaDB |
| Session validation (L1 cache hit) | <0.001 ms | Memory |
| Session validation (L3 DB fallback) | 1.78 ms | PostgreSQL |
| Full auth pipeline (middleware → RBAC) | 1.61 ms | All adapters |
| API request (auth + RBAC + response) | 1.16 ms | SQLite |
| Concurrent throughput | 1,054 RPS | MariaDB |
Reproduce:
bun test tests/benchmarks/auth-performance.test.ts
9. Audit & Compliance
- Crypto-chained logs: Every API mutation is logged with SHA-256 chained audit entries — tamper-evident verification
- security.txt: RFC 9116 standard at
/.well-known/security.txt - EU Directive 2006/114/EC: All competitive comparisons use verifiable public data
- Incident response: Full runbook
- 0 published CVEs — private reporting via GitHub Security Advisories