Zum Inhalt springen
Deutsch

CSRF-Schutz

csrfProtection ist eine zustandslose CSRF-Abwehr: das Token ist payload.hmac(secret, payload) und steckt in einem Cookie mit __Host--Präfix. Sichere Methoden erzeugen/erneuern das Token; unsichere Methoden (POST/PUT/PATCH/DELETE) müssen ein passendes Cookie und einen passenden Header vorweisen.

import { csrfProtection, CsrfOptions, withMiddleware } from 'actor-ts/http';
const csrf = csrfProtection(
CsrfOptions.create().withSecret(process.env.CSRF_SECRET!),
);
const routes = withMiddleware(csrf, appRoutes);

Bei einem sicheren Request wird das Token als (nicht-HttpOnly-)Cookie gesetzt und zusätzlich an den Handler weitergereicht, der es mit readCsrfToken(req) liest und in ein Formularfeld oder <meta>-Tag einsetzt. Der Browser sendet es beim nächsten unsicheren Request im X-CSRF-Token-Header (oder einem konfigurierten Formularfeld) zurück.

Drei getrennte Dinge tragen die Abwehr, und es lohnt sich, genau zu sein, welches davon was leistet:

  • Der HMAC weist Tokens ab, die dieser Server nie ausgestellt hat — Müll, geratene Werte, ein Token aus einem anderen Deployment. Er bindet ein Token an das Server-Secret und an sonst nichts: ein für einen Client ausgestelltes Token verifiziert also für jeden anderen. Er ist nicht das, was das Platzieren von Cookies verhindert.
  • Der __Host--Cookie-Name verhindert das Platzieren — und ist der Default. Ein Browser akzeptiert so ein Cookie nur mit Secure, Path=/ und ohne Domain, was es exakt auf diesen Host festnagelt: eine Geschwister-Subdomain kann es nicht schreiben, eine Klartext-Origin überhaupt nicht.
  • Das Origin/Referer-Gate auf unsicheren Methoden weist den Cross-Site-Request ab, der ein platziertes Paar überhaupt erst mitbrächte.
Builder-MethodeFeldDefault
withSecret(s)secreterforderlich, ≥ 16 Bytes
withCookieName(n)cookieName'__Host-csrf-token'
withHeaderName(n)headerName'x-csrf-token'
withCookie(attrs)cookiePath=/, Secure, SameSite=Lax
withVerifyOrigin(flag?)verifyOrigintrue — zusätzlich Origin/Referer prüfen
withAllowedOrigins(...o)allowedOriginszusätzlich akzeptierte vollständige Origins
withExpectedScheme(s)expectedScheme'https' — bzw. 'http', wenn das Cookie nicht Secure ist
withFormField(name)formFieldNameaus — das Token auch aus einem urlencoded Body-Feld lesen

Der Origin-Check vergleicht vollständige Origins — Scheme, Host und Port — auf beiden Armen: die eigene Origin des Requests und jeden allowedOrigins-Eintrag. https://app.example akzeptiert also weder http://app.example noch https://app.example:8443, und ein Origin: null (eine opake Origin, z. B. aus einem sandboxed iframe) wird immer abgewiesen.

Ein Request nennt seinen Host im Host-Header, aber niemals sein Scheme — TLS kann am Proxy terminieren, und ein weitergereichter Scheme-Header wird vom Client gesetzt, ist also nicht vertrauenswürdig. expectedScheme liefert diese fehlende Hälfte. Default ist 'https'; csrfProtection liest stattdessen 'http', wenn du das Secure-Cookie-Attribut ausdrücklich abschaltest — denn damit ist ein Klartext-HTTP-Betrieb bereits erklärt:

const csrfOptions = CsrfOptions.create()
.withSecret(process.env.CSRF_SECRET!)
// Local dev over plain HTTP: a __Host- cookie cannot be set at all, and
// dropping Secure also switches the expected scheme to http.
.withCookieName('csrf-token')
.withCookie({ secure: false });

Einträge in allowedOrigins müssen vollständige Origins sein ('https://partner.example', nicht 'partner.example'); alles andere wirft beim Bauen der Middleware einen OptionsError, weil es nie passen könnte.

Die leichtgewichtige Alternative — requireSameOrigin

Abschnitt betitelt „Die leichtgewichtige Alternative — requireSameOrigin“

Brauchst du nur Schutz für moderne Browser, weist requireSameOrigin() unsichere-Methoden-Requests ab, deren Origin/Referer nicht die eigene Origin des Requests ist:

import { requireSameOrigin, withMiddleware } from 'actor-ts/http';
const routes = withMiddleware(requireSameOrigin(), appRoutes);

Es nimmt dieselben allowedOrigins und expectedScheme (plus allowMissingOrigin) mit demselben 'https'-Default — eine Klartext-HTTP-Seite braucht SameOriginOptions.create().withExpectedScheme('http'), denn hier gibt es kein Cookie, aus dem sich das ableiten ließe.

csrfProtection ist die Doppel-Absicherung (es führt diesen Check ebenfalls aus); requireSameOrigin allein ist leichter, verlässt sich aber darauf, dass der Browser ein korrektes Origin/Referer sendet.

  • CORS — Umgang mit Cross-Origin-Requests.
  • Sicherheit — wo CSRF im Stack sitzt.