Eine günstige und oft unterschätzte CSRF-Schutz-Schicht: Server prüft den Origin-Header. Eine Anfrage von einer fremden Origin trägt einen Origin: https://evil.example-Header, eine eigene Anfrage Origin: https://app.example. Der Server kann ablehnen, was nicht zur eigenen Origin passt — ohne CSRF-Token, ohne SameSite, billige Defense-in-Depth-Schicht. Dieser Artikel zeigt die drei relevanten Header (Origin, Referer, Sec-Fetch-Site), ihre Eigenheiten und wann sie als alleiniger Schutz ausreichen.

Der Origin-Header

Der Origin-Header wird vom Browser bei den meisten Cross-Origin-Anfragen gesetzt. Er enthält die Origin (Schema + Host + Port), von der die Anfrage stammt — ohne Pfad oder Query.

Beispiel-Anfrage:

HTTP origin-header-example.http
POST /api/transfer HTTP/1.1
Host: bank.example
Origin: https://app.bank.example
Content-Type: application/json

{"to": "...", "amount": 100}

Der Server kann prüfen, ob Origin zur erwarteten Liste gehört:

JavaScript origin-check-server.js
const ALLOWED_ORIGINS = new Set([
  'https://app.bank.example',
  'https://www.bank.example',
]);

app.use((req, res, next) => {
  // Nur für state-changing methods
  if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
    return next();
  }

  const origin = req.headers.origin;
  if (!origin || !ALLOWED_ORIGINS.has(origin)) {
    return res.status(403).send('Origin not allowed');
  }
  next();
});

Eine CSRF-Anfrage von evil.example trägt Origin: https://evil.example — fällt durch.

Wann der Browser den Origin-Header setzt:

  • Bei allen CORS-Anfragen (Cross-Origin XHR, fetch, etc.).
  • Bei Form-Submits per POST/PUT/DELETE (auch Cross-Origin).
  • Bei WebSockets, WebRTC.

Wann er fehlt:

  • Bei klassischer Top-Level-GET-Navigation (Link-Klick, Adressleiste).
  • Bei manchen Same-Origin-GET-Anfragen (Browser-spezifisch).
  • Bei Anfragen aus HTTP-Clients ohne Browser (curl, Postman, Server-Side-Skripte).

Konsequenz: Origin-Check schützt vor allem die POST-Anfragen (also das, was klassisches CSRF ausnutzt). GET-Anfragen mit Side-Effects bleiben ungeschützt — was ohnehin Anti-Pattern ist.

Der Referer-Header

Der Referer-Header ist der historische Vorgänger. Enthält die vollständige URL der Seite, von der die Anfrage stammt — inklusive Pfad und Query.

HTTP referer-header-example.http
Referer: https://app.bank.example/account/dashboard

Vorteile gegenüber Origin:

  • Funktioniert auch bei GET-Anfragen.
  • Sehr lange im Web etabliert — alle Browser senden ihn (mit Konfigurations-Optionen).

Nachteile:

  • Privatsphäre-Bedenken — kompletter URL-Pfad und Query landen beim Empfänger. Browser senden zunehmend gekürzte oder gar keine Referer.
  • Kann vom Browser unterdrückt werdenReferrer-Policy-Header steuert das (siehe referrer-und-analytik Kap 5).
  • Kann fehlen — User-Klicks von HTTPS auf HTTP unterdrücken Referer; manche Browser-Datenschutz-Einstellungen ebenfalls.
  • Spoofing in HTTP-Clients trivial — aber nicht in Browsern bei normaler Nutzung.

Praxis-Empfehlung:

  • Origin-Header als Primärquelle für CSRF-Check.
  • Referer als Fallback, wenn Origin fehlt (manche GET-Anfragen).
  • Beide Header fehlen → Anfrage ablehnen (für sensitive Aktionen) oder durchlassen + zusätzliche Schicht (CSRF-Token).
JavaScript origin-referer-fallback.js
function getRequestOrigin(req) {
  if (req.headers.origin) {
    return req.headers.origin;
  }
  if (req.headers.referer) {
    try {
      const url = new URL(req.headers.referer);
      return `${url.protocol}//${url.host}`;
    } catch {
      return null;
    }
  }
  return null;
}

Sec-Fetch-*-Header: die moderne Variante

Seit Chrome 76 (2019), Firefox 90 (2021), Safari 16.4 (2023) senden Browser eine Familie von Sec-Fetch-*-Headern, die die Herkunft einer Anfrage strukturiert dokumentieren.

Sec-Fetch-Site:

  • same-origin — Anfrage von gleicher Origin.
  • same-site — Anfrage von gleicher Site (gleiche eTLD+1, anderer Subdomain).
  • cross-site — Anfrage von anderer Site.
  • none — Anfrage stammt nicht von einer Webseite (z. B. direktes Adressleisten-Eingeben, Bookmark).

Beispiel-Check:

JavaScript sec-fetch-site-check.js
app.use((req, res, next) => {
  if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
    return next();
  }

  const site = req.headers['sec-fetch-site'];
  // 'none' = direkte Navigation, 'same-origin' = eigene App
  if (site && site !== 'same-origin' && site !== 'none') {
    return res.status(403).send('Cross-site request rejected');
  }
  next();
});

Weitere Sec-Fetch-Header:

  • Sec-Fetch-Modenavigate, cors, no-cors, same-origin, websocket.
  • Sec-Fetch-Destdocument, iframe, image, script, style, etc.
  • Sec-Fetch-User?1 für User-aktivierte Navigation, sonst leer.

Diese Header sind vom Browser garantiert und können vom JavaScript der Seite nicht manipuliert werden. Sie sind ein starkes Signal, um zu prüfen, woher eine Anfrage kommt.

Warum sie wichtig sind:

  • Granularer als Origin/Referer.
  • Strukturiert — Server kann maschinen-lesbar entscheiden.
  • Standardisiert — alle modernen Browser senden sie.

Vorbehalt:

  • Ältere Browser (Pre-2019 Chrome, Pre-2021 Firefox, Pre-2023 Safari) senden sie nicht. Wer eine breite Browser-Basis bedient, kann sich nicht ausschließlich auf Sec-Fetch verlassen.
  • HTTP-Clients (curl, Postman, Server-Side) senden sie meist nicht — manche legitime Cron-Jobs und Server-zu-Server-Calls müssen davon ausgenommen werden.

Praxis-Empfehlung: Sec-Fetch als Bonus-Schicht zusätzlich zu Origin-Header und CSRF-Token. Nicht als alleiniger Schutz.

Edge-Cases und Subdomain-Probleme

Subdomain-Edge-Case:

https://app.bank.example und https://api.bank.example sind verschiedene Origins, aber gleiche Site.

Wenn deine App-Frontend auf app.bank.example läuft und das Backend auf api.bank.example:

  • Origin-Check Origin === 'https://api.bank.example' würde die App ablehnen, weil sie als Origin https://app.bank.example schickt.
  • Korrekte Allow-List muss alle Frontend-Origins enthalten.
JavaScript multi-origin-allow.js
const ALLOWED = new Set([
  'https://app.bank.example',
  'https://m.bank.example',  // mobile-optimiert
  'https://admin.bank.example',
]);

function checkOrigin(req) {
  const origin = req.headers.origin;
  return origin && ALLOWED.has(origin);
}

Wildcard-Erlaubnis: Vorsicht.

Manche Setups nutzen Wildcard-Matching: *.bank.example ist erlaubt. Sehr vorsichtig: wenn user-content.bank.example eine Subdomain mit User-Uploads ist und XSS hat, fließen CSRF-Anfragen mit gültigem Origin durch.

Empfehlung: Explizite Allow-List, keine Wildcards. Wenn Wildcards nötig: nur kontrolllierte Subdomains (admin.*, api.*), nicht solche mit User-Content.

Null-Origin:

In manchen Konstellationen sendet der Browser Origin: null:

  • iframe mit sandbox-Attribut ohne allow-same-origin.
  • Anfragen aus file://-Schema.
  • Redirects in manchen Versionen.

Praxis: Origin: null niemals erlauben in der Allow-List. Behandle null wie cross-origin.

Origin nicht gesetzt — wann?

Bei GET-Top-Level-Navigation und manchen Same-Origin-GET-Anfragen kann der Origin-Header fehlen. Eine zustands-ändernde GET-Anfrage ist ohnehin Anti-Pattern; wenn doch nötig, auf Referer-Fallback.

Origin-Check als alleiniger CSRF-Schutz?

Eine Frage, die in der Web-Sicherheits-Community diskutiert wird: Reicht Origin-Header-Check als alleiniger Schutz?

Pro:

  • Sehr einfach zu implementieren — eine Middleware-Zeile.
  • Stateless — keine Session-Speicherung nötig.
  • Funktioniert für SPAs und APIs.
  • Origin-Header ist vom Browser garantiert, kann nicht von JS auf der Seite manipuliert werden.

Contra:

  • Wenn Origin fehlt (z. B. bei manchen GET-Anfragen, manchen alten Browsern), versagt der Schutz.
  • Subdomain-XSS kann den Schutz aushebeln (Anfrage kommt von erlaubter Subdomain).
  • Mixed Content / HTTPS-Downgrade-Edge-Cases können Origin-Header verlieren.
  • Browser-Bugs sind selten, aber existieren — Origin-Header-Konstruktion war in seltenen Fällen umgehbar.

OWASP-Empfehlung: Origin-Check ist Defense-in-Depth-Schicht, nicht alleiniger Schutz. Kombiniert mit:

  • SameSite=Lax-Cookies als Browser-Schutz.
  • CSRF-Tokens als Anwendungs-Schutz.
  • Sec-Fetch-Site als zusätzlicher Browser-Schutz.

Wer eine moderne API mit Bearer-Token-Auth (JWT in Header) baut, hat strukturell kein CSRF-Problem — Browser sendet den Authorization-Header nicht automatisch cross-site. Aber selbst dort ist Origin-Check ein billiger Bonus.

CORS und Origin — eine häufige Verwechslung

Viele Entwickler:innen verwechseln CORS mit CSRF-Schutz. CORS ist kein CSRF-Schutz — im Gegenteil, es ist ein Mechanismus, um Cross-Origin-Zugriff zu erlauben, der ohne CORS verboten wäre.

Was CORS tut:

  • Erlaubt JavaScript auf app.example, die Antwort von api.other.example zu lesen.
  • Ohne CORS verbietet Same-Origin-Policy das Lesen der Response.

Was CORS NICHT tut:

  • Verhindert nicht, dass die Anfrage überhaupt gestellt wird.
  • Verhindert nicht, dass Side-Effects auf dem Server passieren.

Konkret: Wenn evil.example per fetch POST /api/transfer an bank.example schickt — die Anfrage erreicht den Server und wird ausgeführt, auch wenn bank.example keine CORS-Headers schickt. Der Browser verhindert nur, dass das JavaScript auf evil.example die Response lesen kann.

Konsequenz: Wer denkt „Wir setzen kein Access-Control-Allow-Origin: *, also sind wir CSRF-sicher" — irrt. Server-Side-Origin-Check oder CSRF-Token sind unabhängig nötig.

Vertieft in csrf-in-spas-und-apis — CORS-Mythos-Sektion.

Pragmatische Empfehlung

Für eine moderne Web-App ist die robuste Konfiguration:

Server-side:

JavaScript combined-csrf-defense.js
const ALLOWED_ORIGINS = new Set([
  'https://app.example.com',
  'https://www.example.com',
]);

app.use((req, res, next) => {
  // Nur state-changing methods prüfen
  if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
    return next();
  }

  // 1. Origin-Header-Check
  const origin = req.headers.origin;
  if (origin && !ALLOWED_ORIGINS.has(origin)) {
    return res.status(403).send('Cross-origin request rejected');
  }

  // 2. Sec-Fetch-Site (bonus)
  const site = req.headers['sec-fetch-site'];
  if (site === 'cross-site') {
    return res.status(403).send('Cross-site request rejected');
  }

  // 3. CSRF-Token-Check (siehe csrf-tokens-und-double-submit)
  // ... separate Middleware

  next();
});

Plus Browser-Side:

  • Cookies mit SameSite=Lax (oder Strict für sensitive Sessions).
  • Cookies mit __Host--Präfix für Auth.
  • HTTPS überall.

Damit hat man drei Verteidigungs-Schichten — und jeder einzelne Bypass scheitert an der nächsten.

FAQ

Sollte ich Origin-Check IMMER aktivieren?

Für state-changing Endpunkte: ja. Origin-Check ist billig (eine Middleware-Funktion) und zuverlässig. Bei GET-Endpunkten ohne Side-Effects ist er unnötig. Bei API-Endpunkten mit Bearer-Token-Auth: bonus, aber nicht strikt nötig.

Was, wenn der Origin-Header fehlt?

Bei sensiblen Aktionen ablehnen — sicherer als durchwinken. Bei unkritischen Aktionen (z. B. „Like"-Button) Fallback auf Referer-Check oder durchlassen. Niemals fehlenden Origin als „kommt sicher von der eigenen Site" interpretieren.

Kann ein Angreifer den Origin-Header fälschen?

Im Browser nein — JavaScript kann den Origin-Header nicht überschreiben. Aus einem HTTP-Client (curl, Postman) ja. Aber: ein Angreifer, der curl gegen deine API schießt, hat keinen Browser-Session-Cookie — der CSRF-Vektor selbst ist die Browser-Mitsendung. Wer mit eigenem Tool angreift, hat andere Angriffs-Pfade (z. B. gestohlene Auth-Tokens).

Was ist der Unterschied zwischen Origin und Referer?

Origin ist nur Schema + Host + Port (ohne Pfad). Referer ist die vollständige URL inklusive Pfad und Query. Origin wurde 2010 als Privacy-freundlichere Alternative eingeführt. Beide Header existieren parallel; Origin ist im Auth-Kontext bevorzugt.

Funktioniert Origin-Check auch bei OAuth-Flows?

Eingeschränkt. OAuth-Authorization-Code-Flow läuft typisch über Top-Level-Browser-Redirects — Origin-Header ist dort nicht das primäre Tool. Stattdessen ist der state-Parameter die CSRF-Schutz-Schicht. Vertieft in csrf-in-oauth-und-saml.

Was sage ich Cron-Jobs und Server-zu-Server-Calls?

Diese senden keinen Origin-Header. Lösung: Ausnahme-Pfad für API-Key-authentifizierte oder mTLS-authentifizierte Endpunkte. Diese Endpunkte haben eigene Auth-Mechanismen, brauchen keinen Origin-Check.

Sec-Fetch-Site = none — was bedeutet das?

Browser sendet Sec-Fetch-Site: none bei direkter Navigation — Adressleisten-Eingabe, Bookmark, neuer Tab mit URL. Das ist nicht cross-site, sondern „kein Web-Kontext". Für CSRF-Schutz: none sollte erlaubt sein (klassische Top-Level-Navigation), cross-site abgelehnt.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu CSRF & SameSite

Zur Übersicht