Headers & CORS

Security-Headers, CORS-Konfiguration, routenbewusste Richtlinien und API-Key-Rotationsplan

Das Kit wendet 10 Security-Headers und routenbewusste CORS-Richtlinien über die Middleware-Chain auf jede Antwort an. Diese bilden die äußerste Verteidigungsschicht — sie schützen gegen Clickjacking, MIME-Sniffing, Protokoll-Downgrade-Angriffe und unberechtigte Cross-Origin-Anfragen, bevor dein Anwendungscode überhaupt ausgeführt wird.
Für die übergeordnete Sicherheitsarchitektur siehe Sicherheitsübersicht. Für Rate Limiting und Eingabevalidierung siehe Rate Limiting & Validierung.

Security-Headers

Jede Antwort enthält einen umfassenden Satz an Security-Headers. Die Standardwerte folgen Best Practices der Branche und den OWASP-Empfehlungen:
src/lib/security/security-headers.ts — Default Configuration
const DEFAULT_CONFIG: Required<SecurityHeadersConfig> = {
  hsts: {
    maxAge: 31536000, // 1 year
    includeSubDomains: true,
    preload: true,
  },
  xFrameOptions: 'SAMEORIGIN',
  noSniff: true,
  dnsPrefetchControl: 'on',
  referrerPolicy: 'strict-origin-when-cross-origin',
  permissionsPolicy: {
    camera: [],
    microphone: ['self'],
    geolocation: [],
    'interest-cohort': [], // Disable FLoC
    payment: [],
    usb: [],
  },
}

Header-Referenz

HeaderStandardwertZweck
Strict-Transport-Securitymax-age=31536000; includeSubDomains; preloadErzwingt HTTPS für 1 Jahr, inkl. Subdomains
X-Frame-OptionsSAMEORIGIN (Seiten) / DENY (API)Verhindert Clickjacking durch Kontrolle des Iframe-Einbettens
X-Content-Type-OptionsnosniffVerhindert MIME-Sniffing von Antworten durch Browser
X-DNS-Prefetch-ControlonAktiviert DNS-Prefetching für verlinkte Domains
X-XSS-Protection1; mode=blockLegacy-XSS-Filter für ältere Browser
Referrer-Policystrict-origin-when-cross-originSteuert den Referrer-Header bei Cross-Origin-Anfragen
Permissions-Policycamera=(), microphone=(self), geolocation=(), interest-cohort=(), payment=(), usb=()Schränkt Browser-APIs ein — Mikrofon für Same-Origin-Audioeingabe erlaubt
X-Permitted-Cross-Domain-PoliciesnoneVerhindert das Laden von Daten durch Adobe Flash/PDF
Cross-Origin-Opener-Policysame-origin-allow-popupsIsoliert den Browsing-Kontext (Spectre-Schutz)
Cross-Origin-Resource-Policysame-site (Seiten) / same-origin (API)Verhindert das Lesen von Ressourcen durch andere Origins

Routenbewusste Headers

Einige Headers verwenden unterschiedliche Richtlinien je nach Routentyp. API-Routen erhalten strengere Einstellungen, da sie niemals eingebettet oder von externen Origins aufgerufen werden sollten:
HeaderSeiten-Routen (/, /dashboard)API-Routen (/api/*)
X-Frame-OptionsSAMEORIGIN — ermöglicht Einbettung in Same-Origin-IframesDENY — kein Iframe-Einbetten überhaupt
Cross-Origin-Resource-Policysame-site — zugänglich von derselben Sitesame-origin — nur von der exakt gleichen Origin zugänglich
Diese Unterscheidung stellt sicher, dass deine Marketing-Seiten und das Dashboard intern Iframes verwenden können (z. B. für eingebettete Widgets), während API-Endpunkte vollständig abgeschottet sind.
src/lib/security/security-headers.ts — Route-Aware X-Frame-Options
// 2. X-Frame-Options
  // API routes: DENY (no framing)
  // Other routes: SAMEORIGIN (same-origin framing only)
  if (pathname.startsWith('/api/')) {
    headers.set('X-Frame-Options', 'DENY')
  } else {
    const xFrameOptions = mergedConfig.xFrameOptions
    if (typeof xFrameOptions === 'string') {
      headers.set('X-Frame-Options', xFrameOptions)
    } else {
      headers.set('X-Frame-Options', `ALLOW-FROM ${xFrameOptions.allow}`)
    }
  }

CORS-Konfiguration

Cross-Origin Resource Sharing (CORS) steuert, welche externen Domains Anfragen an deine API stellen können. Das Kit definiert drei CORS-Profile basierend auf dem Routentyp:
src/lib/security/cors-middleware.ts — CORS Configurations
const CORS_CONFIGS: Record<string, CORSConfig> = {
  // Public API routes - Open CORS for public endpoints
  public: {
    allowedOrigins: ['*'],
    allowedMethods: ['GET', 'POST', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: false,
    maxAge: 86400, // 24 hours
  },

  // Protected API routes - Controlled origins
  api: {
    allowedOrigins: process.env.ALLOWED_ORIGINS?.split(',') || [
      process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
    ],
    allowedMethods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,
    maxAge: 86400,
  },

  // Webhook routes - Accept from any origin (webhooks don't send Origin header)
  webhook: {
    allowedOrigins: ['*'],
    allowedMethods: ['POST', 'OPTIONS'],
    allowedHeaders: [
      'Content-Type',
      'Authorization',
      'X-Signature',
      'Webhook-Signature',
      'Svix-Id',
      'Svix-Timestamp',
      'Svix-Signature',
    ],
    credentials: false,
    maxAge: 3600, // 1 hour
  },
}

CORS-Profil-Übersicht

ProfilRoutenErlaubte OriginsMethodenCredentialsMax Age
public/api/pricing, /api/health, Nicht-API-Routen* (beliebige Origin)GET, POST, OPTIONSNein24 Stunden
api/api/* (geschützte Endpunkte)ALLOWED_ORIGINS Env-Variable oder App-URLGET, POST, PATCH, DELETE, OPTIONSJa24 Stunden
webhook/api/webhooks/** (beliebige Origin)POST, OPTIONSNein1 Stunde

So funktioniert die Routentyp-Erkennung

Die CORS-Middleware bestimmt das Profil, indem sie den Anfrage-Pfadnamen der Reihe nach prüft:
src/lib/security/cors-middleware.ts — Route Type Detection
function getRouteType(pathname: string): keyof typeof CORS_CONFIGS {
  if (pathname.startsWith('/api/webhooks/')) {
    return 'webhook'
  }
  if (
    pathname.startsWith('/api/pricing') ||
    pathname.startsWith('/api/health')
  ) {
    return 'public'
  }
  if (pathname.startsWith('/api/')) {
    return 'api'
  }
  return 'public' // Default to public for non-API routes
}
Die erste passende Regel gewinnt. Webhook-Routen werden vor allgemeinen API-Routen geprüft, da /api/webhooks/ eine Teilmenge von /api/ ist.

CORS-Umgebungsvariablen

Geschützte API-Routen verwenden die Umgebungsvariable ALLOWED_ORIGINS, um zu bestimmen, welche Domains Cross-Origin-Anfragen stellen dürfen:
bash
# Single origin
ALLOWED_ORIGINS=https://myapp.com

# Multiple origins (comma-separated)
ALLOWED_ORIGINS=https://myapp.com,https://staging.myapp.com,https://admin.myapp.com

# Wildcard subdomains are supported in the origin matching
# (configured in code, not via env var)
Wenn ALLOWED_ORIGINS nicht gesetzt ist, fällt das System auf NEXT_PUBLIC_APP_URL zurück (oder http://localhost:3000, falls das ebenfalls fehlt). Das bedeutet, die lokale Entwicklung funktioniert ohne jede CORS-Konfiguration sofort.

Preflight-Handling

Die CORS-Middleware fängt OPTIONS-Anfragen (CORS-Preflight) ab und gibt eine 204 No Content-Antwort mit den entsprechenden CORS-Headers zurück. Das schließt die Middleware-Chain kurz — die Anfrage erreicht weder die Clerk-Authentifizierung noch deinen Route-Handler:
OPTIONS /api/upload HTTP/1.1
Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

──────────────────────────────────

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
Vary: Origin
Der Vary: Origin-Header stellt sicher, dass Caches separate Antworten für verschiedene Origins speichern. Das Max-Age von 86400 Sekunden (24 Stunden) bedeutet, dass Browser das Preflight-Ergebnis cachen, was die Anzahl der Preflight-Anfragen reduziert.

CORS für neue Routen hinzufügen

Wenn du neue API-Routen hinzufügst, erben diese automatisch das richtige CORS-Profil basierend auf dem Pfad:
  • /api/webhooks/my-service — Erhält das webhook-Profil (offenes CORS, nur POST)
  • /api/my-feature — Erhält das api-Profil (eingeschränkte Origins, Credentials)
  • /api/health-check — Erhält das api-Profil, sofern du es nicht zur öffentlichen Liste hinzufügst
Um eine neue Route mit dem public-Profil zu versehen, füge sie der Erkennungsfunktion hinzu:
typescript
// In apps/boilerplate/src/lib/security/cors-middleware.ts
function getRouteType(pathname: string): keyof typeof CORS_CONFIGS {
  if (pathname.startsWith('/api/webhooks/')) return 'webhook'
  if (
    pathname.startsWith('/api/pricing') ||
    pathname.startsWith('/api/health') ||
    pathname.startsWith('/api/my-public-endpoint')  // Add here
  ) {
    return 'public'
  }
  if (pathname.startsWith('/api/')) return 'api'
  return 'public'
}

API-Key-Verwaltung

Das Kit verwendet mehrere Drittanbieter-API-Keys, die regelmäßig rotiert werden sollten, um den Schaden bei kompromittierten Zugangsdaten zu minimieren:

Rotationsplan

KeyRotationszeitraumPrioritätDienst
CLERK_SECRET_KEY90 TageKritischAuthentifizierung
LEMON_SQUEEZY_API_KEY90 TageKritischZahlungen
RESEND_API_KEY90 TageKritischE-Mail
UPSTASH_REDIS_REST_TOKEN180 TageStandardRate Limiting / Caching
BLOB_READ_WRITE_TOKEN180 TageStandardDateispeicher
OPENAI_API_KEY180 TageStandardAI-Anbieter
ANTHROPIC_API_KEY180 TageStandardAI-Anbieter
GOOGLE_GENERATIVE_AI_API_KEY180 TageStandardAI-Anbieter
Kritische Keys (90-Tage-Rotation) sind Keys, die bei Kompromittierung direkt zu finanziellem Schaden oder der Offenlegung von Nutzerdaten führen können. Standard-Keys (180-Tage-Rotation) haben einen engeren Wirkungsbereich.

Rotationsprozess

  1. Neuen Key im Dashboard des Anbieters generieren
  2. Den neuen Key zur Deployment-Umgebung hinzufügen (Vercel etc.)
  3. Mit dem neuen Key deployen — der alte Key bleibt während der Übergangsphase gültig
  4. Prüfen, ob der neue Key in der Produktion funktioniert (Logs auf Auth-Fehler prüfen)
  5. Den alten Key im Dashboard des Anbieters widerrufen

Sicherheitsrelevante Umgebungsvariablen

VariableErforderlichStandardZweck
ALLOWED_ORIGINSNeinNEXT_PUBLIC_APP_URLKommagetrennte Liste erlaubter CORS-Origins für geschützte API-Routen
NEXT_PUBLIC_APP_URLJahttp://localhost:3000Anwendungs-URL — als CORS-Fallback und in Security-Headers verwendet
CRON_SECRETNeinSecret zur Authentifizierung von Cron-Job-Anfragen (/api/cron/*)
CLERK_WEBHOOK_SECRETJaSVIX-Secret zur Verifizierung von Clerk-Webhook-Signaturen
LEMON_SQUEEZY_WEBHOOK_SECRETJaSecret zur Verifizierung von Lemon-Squeezy-Webhook-Signaturen

Wichtige Dateien

DateiZweck
apps/boilerplate/src/lib/security/security-headers.tsGenerierung und Anwendung von Security-Headers
apps/boilerplate/src/lib/security/cors-middleware.tsCORS-Konfiguration, Preflight-Handling und Antwort-Headers
apps/boilerplate/src/middleware.tsMiddleware-Chain, die Headers und CORS auf jede Anfrage anwendet