Skip to content

Documentation

API Security & Token Hardening

Enterprise-grade API security architecture: 4-layer defense-in-depth, bearer tokens, tenant isolation, rate limiting, CSRF, and CSP.

6/26/2026
9 min read Edit on GitHub

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 & CSRF Cookies

Browser-based authentication and mutation security rely on hardened cookie structures:

Cookie Name (Prod / Dev) Attributes Purpose
Session Cookie __Host-auth_sessions / auth_sessions httpOnly: true, secure: true, sameSite: "strict", path: "/" Holds the 32-character CSPRNG session token. Inaccessible to JavaScript (XSS mitigation).
CSRF Cookie __Host-csrf_token / csrf_token httpOnly: false, secure: true, sameSite: "strict", path: "/" 256-bit CSPRNG token read by client fetch utilities to attach the X-CSRF-Token header.
  • __Host- Prefix Contract (RFC 6265bis): In HTTPS environments, cookies use the __Host- prefix, requiring secure: true, path: "/", and forbidding domain delegation. This guarantees subdomains cannot plant, spoof, or overwrite cookies.
  • Cookie Deletion Parity: clearSessionCookies sets identical cookie attributes (path: "/", sameSite: "strict", secure: true, httpOnly: true) upon logout or invalidation across all prefix variants (__Host-, __Secure-, plain), ensuring complete cookie removal across all browsers.
  • Session Lifecycle & Invalidation:
    • Auto-rotated upon login and session renewal.
    • Password change or logout triggers cascading invalidation across all devices.
    • 3-layer caching: Memory (L1) → Redis (L2) → Database (L3) with credential-free memory caching.

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:admin or 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 handle-rate-limit.ts (RateLimiter) — 5 attempts → 15-min lockout (HTTP 423)
API rate limit handle-rate-limit.ts Per-IP + per-tenant fixed window; commerce lane isolated from admin mutations (X-RateLimit-Lane)
WAF endpoint limits SecurityResponseService rate-limiter-flexible per path; /api/commerce is 60/min (prefix-matched)
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. In NODE_ENV=production, GraphQL endpoints use the same strict CSP as REST APIs.


7. CSRF Protection & Request Verification Architecture

SveltyCMS enforces a hardened Double-Submit Cookie Pattern combined with multi-layer origin and referer verification on all mutating requests (POST, PUT, PATCH, DELETE).

7.1 The Double-Submit Handshake

Client Request (Mutation)
  ├─ Cookie: __Host-csrf_token=<token_A> (or csrf_token in dev)
  ├─ Header: X-CSRF-Token: <token_A>
  └─ Header: Origin / Referer: https://example.com


[1] Safe-Method Bypass (GET / HEAD / OPTIONS) ───► 0µs cost, CSRF skipped

[2] Token-Bound Bypass (Bearer / API Key / SCIM) ──► CSRF skipped (stateless)

[3] Same-Origin Fast-Path (Origin/Referer === Host) ──► Validated in nanoseconds

[4] Constant-Time Token Comparison ──────────────► XOR accumulator (no timing leak)

[5] Single-Use Token Rotation ───────────────────► Invalidate old token, issue fresh cookie
  1. Token Generation: 256-bit CSPRNG token (crypto.getRandomValues()) placed in the csrf_token / __Host-csrf_token cookie (sameSite: "strict", httpOnly: false, path: "/").
  2. Double Submission: Browser clients extract the cookie value and submit it in the X-CSRF-Token HTTP header on every mutating API request (clientJsonHeaders()).
  3. Constant-Time Comparison: validateCsrfToken validates the header token against the cookie using a bitwise XOR accumulator:
    let result = 0;
    for (let i = 0; i < cookieToken.length; i++) {
      result |= cookieToken.charCodeAt(i) ^ tokenToValidate.charCodeAt(i);
    }
    return result === 0;
  4. Single-Use Rotation: On every successful mutation, the server immediately invalidates the consumed CSRF token and issues a fresh token in the response cookie, preventing replay attacks.
  5. Same-Origin Fast-Path: Same-origin requests (originUrl.host === host or refererUrl.host === host) pass verification via nanosecond string comparison.
  6. Stateless Exemption: Machine-to-machine requests utilizing Bearer tokens, API Keys, or SCIM credentials are automatically exempt from cookie CSRF (they do not rely on ambient browser cookies).

7.2 Check Parity: Read Paths vs. Mutation Paths

To ensure optimal throughput and prevent performance bottlenecks, SveltyCMS maintains strict check parity without duplicate or redundant operations:

Execution Stage Read Paths (findById, find, list) Mutation Paths (create, update, bulk)
CSRF Verification 0 checks (0µs overhead) — Safe HTTP verbs (GET/HEAD) bypass CSRF. 1 check at the API gateway layer (validateCsrfForRequest).
Authentication & RBAC Single check in handleAuthentication + handleAuthorization (cached in locals). Single check in middleware + endpoint permission mapping (_checkEndpointPermission).
Schema Resolution Single lookup from hot in-memory LRU (schemaOf). Single lookup from hot in-memory LRU (schemaOf).
Payload Preparation Skipped on read. Single unified pass (prepareWritePayload) combining sanitization, type validation, and write guards.
Database Execution Single direct prepared query (findById / findOne). Single atomic prepared statement (insert / update with RETURNING).
Post-Write Processing None. Detached / non-blocking background execution (schedulePostWrite) — does not delay response.

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

Related

securityauthapihardeningtokensrate-limiting
Was this page helpful?