Synexa
How It Works

Security

Security is built into the foundation of this kit, not bolted on. This is a rundown of what ships enabled by default — including the subtle correctness guarantees that are easy to get wrong and expensive to discover in production.

Authentication & sessions

  • HttpOnly session cookies. The __session cookie is HttpOnly, Secure (in production), and SameSite=Lax, so client-side scripts cannot read it and browsers withhold it from most cross-site requests. Mutating cookie-auth routes also validate the request origin; SameSite is defense-in-depth, not the only CSRF control.
  • Revocation-aware. Every protected request re-verifies the session against the Firebase Admin SDK with checkRevoked=true, so deactivating or signing a user out takes effect on their next request — not whenever a token happens to expire.
  • One account per email. With Firebase's required default one-account-per-email setting, Google and email-OTP resolve to one identity. The app additionally refuses to create a second application profile for an email if provider linking is misconfigured or cannot be resolved safely.
  • Brute-force limits. OTP verification is capped per code (attempts tracked inside a transaction) and per IP (10/min); OTP sends are capped at 10/hour/IP. Codes are stored only as salted SHA-256 hashes and are never readable by clients.

Authorization & entitlements

  • Entitlements are server-only. role and pricingTier can only ever be written by the server (Admin SDK) — Firestore rules make them unwritable from the client, so a signed-in user can never grant themselves admin or Pro.
  • Stripe is the single source of truth for billing. Access is derived from live, webhook-authoritative Stripe subscription state (and continuously reconciled), never from a client claim.
  • Admin bootstrap is verified-email-gated. The SUPER_ADMIN_EMAILS auto-grant applies only to a provider-verified email.
  • The last usable admin can't be removed. Every path that would strip admin access — an admin deleting, demoting, or deactivating another admin, and an admin deleting their own account through account settings — is guarded so the app can never be left with zero usable admins. "Usable" means role admin, active, and not mid-deletion, matching the Firestore isAdmin() rule.

Firestore & Storage rules

  • Pre-configured, tested rules enforce that users read/write only their own documents, that entitlement/role fields are server-only, and that internal collections (OTP hashes, audit logs, billing state, deletion jobs) are neither readable nor writable by clients.
  • Default-deny is in place and Storage is locked down. Rules live in firestore.rules / storage.rules (deploy via the Firebase CLI). A rules test suite runs against the Firebase emulator via npm run test:rules.

API & network

  • Edge route guard. proxy.ts redirects unauthenticated requests away from protected routes before they reach a function; full verification still happens server-side.
  • CSRF protection. Cookie-authenticated mutating routes origin-validate requests (lib/csrf.ts) and fail closed; Bearer-only API calls require an Authorization header, which a cross-site form cannot attach. Combined with SameSite=Lax, a cross-site page can't act on a user's session.
  • Rate limiting, on by default. Sensitive endpoints (OTP, contact, export) use durable, fail-closed, Firestore-backed limits. Limiting is enabled in every environment unless you explicitly set RATE_LIMIT_DISABLED=true — it is never silently switched off by a misconfigured NODE_ENV.
  • Input validation. Routes validate and sanitize input with Zod (lib/validators.ts).
  • No internal error leakage. Public endpoints return generic messages; the underlying cause stays in the server logs.

Response headers

next.config.mjs sets a hardened header set on every response:

  • Content-Security-Policy — including frame-ancestors 'none', base-uri 'self', form-action 'self', object-src 'none', and script-src-attr 'none'. script-src still allows inline script elements for Next/theme JSON-LD compatibility; migrate to nonces or hashes before claiming a nonce-strict CSP.
  • Strict-Transport-Security (HSTS, 2 years, preload)
  • X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy, Permissions-Policy

Data lifecycle & privacy

  • Durable account deletion. Deletion makes active or ambiguous Checkout creation terminal and cancels Stripe before disabling Auth, then writes a tombstone and removes data through a resumable, leased job. Failed provider steps leave the profile recoverable and are retried by the authenticated scheduled recovery route; delayed Stripe events cannot recreate it.
  • GDPR. One-click data export, a cookie-consent banner, and privacy/terms templates are included.
  • PII-redacting logs. Emails are masked and secret-like fields are dropped before anything is written to logs.

Operations

  • Secrets (Firebase private key, Stripe secret, CRON_SECRET) live in the host's environment, never in git.
  • Timing-safe secret checks. The cron secret is compared with a constant-time digest comparison.
  • Error alerting. Unhandled server errors fire a throttled, deduplicated ops alert (email + an ops_alerts record) through the onRequestError instrumentation hook — and it's ready to run alongside a full error monitor such as Sentry.
  • Dependency gate. Security-sensitive dependency state and the remaining release blocker are tracked in DEPENDENCY_AUDIT.md.

Next: Read about Customization.