warum ein einziger Endpunkt andere Regeln braucht
Bei REST-APIs verlassen sich viele Teams auf simple Content-Type-Prüfungen oder auf die Annahme, dass Cross-Site-Requests ohnehin nur GET-Anfragen betreffen. GraphQL bündelt Queries und Mutations hinter einem einzigen POST-Endpunkt und macht diese Annahmen wertlos. Wer eine GraphQL-API mit Cookie-basierten Sessions betreibt, braucht einen expliziten CSRF-Schutz, der die Besonderheiten des einzelnen Endpunkts berücksichtigt.
Inhaltsverzeichnis
- 1. Was CSRF ist und wann es überhaupt relevant wird
- 2. Warum GraphQL andere Annahmen bricht als REST
- 3. Ein konkreter CSRF-Angriff gegen eine GraphQL-Mutation
- 4. Content-Type-Prüfung als erste Verteidigungslinie
- 5. SameSite-Cookies richtig konfigurieren
- 6. CSRF-Tokens für GraphQL-Mutations
- 7. Warum Bearer-Token-Auth CSRF strukturell umgeht
- 8. CSRF-Schutz bei Magento GraphQL im Kontext
- 9. Schutzmaßnahmen im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Was CSRF ist und wann es überhaupt relevant wird
Cross-Site Request Forgery, kurz CSRF, nutzt aus, dass Browser Cookies automatisch bei jedem Request an die passende Domain mitschicken, unabhängig davon, welche Website die Anfrage ausgelöst hat. Besucht ein eingeloggter Nutzer eine bösartige Seite, kann diese im Hintergrund einen Request an eine andere Domain auslösen, etwa über ein automatisch abgeschicktes Formular, und der Browser hängt die gültigen Session-Cookies automatisch an. Für CSRF-Schutz bei GraphQL ist entscheidend: Das Risiko existiert ausschließlich bei Cookie-basierter Authentifizierung, nicht bei Auth-Headern.
Läuft eine GraphQL-API ausschließlich mit Bearer-Tokens im Authorization-Header, entfällt das klassische CSRF-Risiko strukturell, weil ein Angreifer diesen Header von einer fremden Domain aus nicht setzen kann, ohne die Same-Origin-Policy zu umgehen. Sobald aber Session-Cookies zum Einsatz kommen, etwa bei serverseitig gerenderten Storefronts mit klassischer PHP-Session oder bei GraphQL-APIs, die aus Bequemlichkeit auf Cookie-Auth setzen, wird CSRF-Schutz für GraphQL zur Pflichtaufgabe.
2. Warum GraphQL andere Annahmen bricht als REST
Klassischer REST-CSRF-Schutz verlässt sich häufig auf zwei Annahmen: Erstens, dass state-verändernde Operationen über POST, PUT, PATCH oder DELETE laufen, während GET-Requests sicher sind, weil sie idempotent sein sollten. Zweitens, dass verschiedene Endpunkte unterschiedliche URLs haben, sodass gezielte Angriffe auf bestimmte Aktionen abzielen müssen. Beide Annahmen greifen bei GraphQL nicht mehr, denn CSRF-Schutz für GraphQL muss berücksichtigen, dass praktisch alle Operationen, Queries wie Mutations, über denselben einzigen POST-Endpunkt laufen.
Das bedeutet: Ein Angreifer muss nicht mehr verschiedene URLs für verschiedene Aktionen kennen, sondern nur den einen GraphQL-Endpunkt und die Query- oder Mutation-Struktur, die er ausführen will. Manche GraphQL-Server akzeptieren zusätzlich GET-Requests mit der Query als URL-Parameter, was das REST-Prinzip "GET ist sicher" komplett aushebelt, wenn dieselbe GET-Route auch Mutations zulässt oder wenn eine an sich harmlose Query sensible Daten preisgibt. CSRF-Schutz für GraphQL muss also grundsätzlich anders gedacht werden als bei REST.
3. Ein konkreter CSRF-Angriff gegen eine GraphQL-Mutation
Ein klassischer Angriffsvektor ohne CSRF-Schutz für GraphQL nutzt aus, dass HTML-Formulare Cross-Origin-POST-Requests mit dem Content-Type application/x-www-form-urlencoded auslösen können, ganz ohne JavaScript und ohne CORS-Preflight, da dieser Content-Type als "simple request" gilt. Akzeptiert der GraphQL-Server Query-Strings auch unter diesem Content-Type, statt ausschließlich application/json zu verlangen, kann ein Angreifer eine Mutation wie eine Adressänderung oder eine Zahlungsmethode-Aktualisierung über ein unsichtbares, automatisch abgeschicktes Formular auslösen.
<!-- Malicious page hosted on attacker.example -->
<!-- Auto-submits a cross-site POST that rides the victim's session cookie -->
<form id="csrf-form" action="https://shop.mironsoft.de/graphql" method="POST"
enctype="application/x-www-form-urlencoded">
<input type="hidden" name="query"
value="mutation { updateCustomerEmail(email: "attacker@evil.example") { id } }">
</form>
<script>document.getElementById('csrf-form').submit();</script>
Ohne Content-Type-Prüfung und ohne CSRF-Token verarbeitet der Server diese Anfrage, weil das gültige Session-Cookie automatisch mitgeschickt wird. Der Nutzer sieht davon nichts, während im Hintergrund eine sensible Änderung an seinem Account vorgenommen wird. Dieses Beispiel zeigt, warum CSRF-Schutz für GraphQL nicht optional ist, sobald Cookie-Sessions im Spiel sind.
4. Content-Type-Prüfung als erste Verteidigungslinie
Die einfachste und wirksamste Maßnahme für CSRF-Schutz bei GraphQL ist, ausschließlich Requests mit dem Content-Type application/json zu akzeptieren und alle anderen Content-Types strikt abzulehnen. Da application/json nicht zu den "simple request"-Content-Types der Fetch-Spezifikation gehört, erzwingt der Browser bei Cross-Origin-Requests mit diesem Content-Type automatisch einen CORS-Preflight-Request. Der Server kann diesen Preflight nutzen, um die Anfrage abzulehnen, bevor der eigentliche Request überhaupt beim Server ankommt.
Diese Maßnahme allein blockiert bereits den Großteil naiver CSRF-Angriffe über HTML-Formulare, da Formulare nur application/x-www-form-urlencoded, multipart/form-data oder text/plain als Content-Type senden können, niemals application/json. Apollo Server bietet dafür die eingebaute csrfPrevention-Option, die genau diese Content-Type-Prüfung übernimmt und zusätzlich einen benutzerdefinierten Header wie Apollo-Require-Preflight verlangt, um auch Requests mit manipuliertem Content-Type-Header abzufangen.
// server.js — Apollo Server's built-in CSRF prevention
import { ApolloServer } from '@apollo/server';
const server = new ApolloServer({
schema,
// Rejects requests without a proper JSON content-type or a required
// non-simple header, forcing a CORS preflight for cross-origin calls
csrfPrevention: true,
});
// client.ts — clients must now send a non-simple header explicitly
fetch('https://api.mironsoft.de/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Apollo-Require-Preflight': 'true',
},
credentials: 'include',
body: JSON.stringify({ query: '{ me { id } }' }),
});
5. SameSite-Cookies richtig konfigurieren
Das Cookie-Attribut SameSite ist eine zweite, unabhängige Verteidigungslinie für CSRF-Schutz bei GraphQL. Mit SameSite=Lax, dem Standardwert moderner Browser, werden Cookies bei den meisten Cross-Site-Requests nicht mitgeschickt, mit Ausnahme von Top-Level-Navigationen über GET. Das reicht aus, um automatisch abgeschickte Cross-Site-POST-Formulare abzublocken, ohne die legitime Navigation zwischen Seiten zu beeinträchtigen. Für maximale Sicherheit lässt sich SameSite=Strict setzen, was aber auch legitime Links von externen Seiten auf die eigene, eingeloggte Session bricht.
Wichtig für CSRF-Schutz bei GraphQL in Multi-Domain-Setups, etwa wenn Frontend und GraphQL-API auf unterschiedlichen Subdomains laufen: SameSite=Lax gilt bereits als Cross-Site zwischen unterschiedlichen Subdomains derselben Registrable Domain nicht, solange die Cookie-Domain korrekt auf die übergeordnete Domain gesetzt ist. Läuft die API jedoch auf einer komplett anderen Domain, etwa für Headless-Setups mit separatem Hosting, braucht es zusätzlich das Secure-Attribut und explizite CORS-Konfiguration, damit SameSite-Schutz und Cross-Origin-Zugriff sauber zusammenspielen.
// cookie-config.js — setting the session cookie with SameSite and Secure
res.cookie('PHPSESSID', sessionId, {
httpOnly: true,
secure: true, // only sent over HTTPS
sameSite: 'lax', // blocks cross-site POST form submissions
domain: '.mironsoft.de', // shared across subdomains, not cross-site
maxAge: 1000 * 60 * 60 * 2,
});
// Nginx equivalent when cookies are set at the proxy layer
// add_header Set-Cookie "PHPSESSID=$upstream_cookie; SameSite=Lax; Secure; HttpOnly";
6. CSRF-Tokens für GraphQL-Mutations
Für Umgebungen, in denen Content-Type-Checks und SameSite-Cookies allein nicht ausreichen, etwa bei älteren Browsern oder besonders sensiblen Mutations wie Zahlungsänderungen, bleibt das klassische CSRF-Token-Pattern eine robuste Ergänzung. Der Server generiert bei Auslieferung der Seite ein zufälliges Token, das sowohl in einem Cookie als auch in einem versteckten Formularfeld oder JavaScript-zugänglichen Meta-Tag landet. Jede GraphQL-Mutation muss dieses Token als zusätzlichen Header mitschicken, den ein Angreifer über eine fremde Domain nicht auslesen kann, da Same-Origin-Policy den Zugriff auf das Cookie oder den DOM der Zielseite verhindert.
Die Implementierung bei GraphQL unterscheidet sich kaum von REST, mit einer Ausnahme: Da alle Mutations über denselben Endpunkt laufen, reicht eine einzige zentrale Middleware, die das Token bei jedem eingehenden POST-Request prüft, statt die Prüfung pro Route zu duplizieren. Diese Zentralisierung ist einer der wenigen Vorteile, den der einzelne GraphQL-Endpunkt für CSRF-Schutz bei GraphQL gegenüber vielen verteilten REST-Routen bietet.
// csrf-middleware.js — one central check for the single GraphQL endpoint
function csrfTokenMiddleware(req, res, next) {
// Only mutations change state, but checking every POST keeps this simple
const cookieToken = req.cookies['csrf-token'];
const headerToken = req.headers['x-csrf-token'];
if (!cookieToken || !headerToken || cookieToken !== headerToken) {
return res.status(403).json({
errors: [{ message: 'Invalid or missing CSRF token', extensions: { code: 'CSRF_TOKEN_INVALID' } }],
});
}
next();
}
app.post('/graphql', csrfTokenMiddleware, graphqlHandler);
7. Warum Bearer-Token-Auth CSRF strukturell umgeht
Der wirksamste, wenn auch architektonisch weitreichendste Schutz gegen CSRF ist der komplette Verzicht auf Cookie-basierte Authentifizierung zugunsten von Bearer-Tokens im Authorization-Header. Da Browser Header nicht automatisch cross-origin mitschicken, so wie sie es bei Cookies tun, kann ein Angreifer den Authorization-Header nicht fälschen, ohne selbst Zugriff auf den Token zu haben. Für neue GraphQL-APIs ohne Legacy-Cookie-Anforderungen ist Bearer-Token-Auth deshalb oft die einfachere Lösung für CSRF-Schutz bei GraphQL, weil das gesamte Problem strukturell entfällt, statt es mit mehreren Schutzschichten einzudämmen.
Der Kompromiss: Bearer-Tokens müssen client-seitig gespeichert werden, meist im lokalen Speicher des Browsers, was wiederum ein anderes Risiko öffnet, nämlich Cross-Site Scripting, das den Token direkt auslesen kann. Für Magento-Storefronts, die ohnehin auf Session-Cookies für den Warenkorb angewiesen sind, ist ein reiner Wechsel zu Bearer-Tokens selten praktikabel, weshalb dort Content-Type-Checks, SameSite-Cookies und CSRF-Tokens die realistischere Kombination bleiben.
8. CSRF-Schutz bei Magento GraphQL im Kontext
Magentos natives GraphQL-Modul nutzt für eingeloggte Storefront-Kunden häufig eine Kombination aus Bearer-Token für die Customer-API und Session-Cookie für den Warenkorb-Kontext, was CSRF-Schutz bei GraphQL in Magento-Umgebungen zu einer besonderen Herausforderung macht, da beide Auth-Mechanismen gleichzeitig aktiv sein können. Mutations, die auf das Quote-Cookie zugreifen, etwa addProductsToCart, sind potenziell CSRF-anfällig, wenn der Endpunkt keine Content-Type-Prüfung erzwingt, selbst wenn die reine Customer-Authentifizierung über Bearer-Token läuft.
In der Praxis empfiehlt sich für Magento-GraphQL-Setups, eine eigene Middleware oder ein Plugin auf dem GraphQL-Controller zu implementieren, das den Content-Type strikt auf application/json prüft und zusätzlich das SameSite-Attribut des Warenkorb-Cookies kontrolliert, statt sich auf Magentos Standard-Konfiguration zu verlassen, die primär für klassische Formular-basierte CSRF-Angriffe im Adminbereich ausgelegt ist, nicht für den GraphQL-Endpunkt selbst.
<?php
declare(strict_types=1);
namespace Mironsoft\GraphQlSecurity\Plugin;
use Magento\Framework\App\Request\Http as HttpRequest;
use Magento\Framework\Webapi\Exception as WebapiException;
/**
* Enforces a strict JSON content-type check on the GraphQL controller
* to prevent form-based CSRF attacks against cart and customer mutations.
*/
class CsrfContentTypeGuardPlugin
{
/**
* Rejects any GraphQL request that is not sent as application/json.
*
* @param \Magento\GraphQl\Controller\GraphQl $subject
* @param HttpRequest $request
* @return void
* @throws WebapiException
*/
public function beforeDispatch(
\Magento\GraphQl\Controller\GraphQl $subject,
HttpRequest $request
): void {
$contentType = (string) $request->getHeader('Content-Type');
if (!str_starts_with($contentType, 'application/json')) {
throw new WebapiException(__('Invalid content type for GraphQL request'), 0, 403);
}
}
}
9. Schutzmaßnahmen im Vergleich
Die folgende Tabelle vergleicht die vorgestellten Maßnahmen für CSRF-Schutz bei GraphQL nach Wirksamkeit und Implementierungsaufwand.
| Maßnahme | Wirksamkeit | Aufwand | Nebenwirkungen |
|---|---|---|---|
| Content-Type-Prüfung | Hoch | Sehr niedrig | Keine bei korrekten Clients |
| SameSite=Lax Cookies | Hoch | Niedrig | Kaum, moderne Browser-Unterstützung gut |
| CSRF-Token | Sehr hoch | Mittel | Zusätzliche Client-Logik nötig |
| Bearer-Token statt Cookies | Strukturell immun | Hoch (Architekturwechsel) | XSS-Risiko bei Token-Speicherung |
Für die meisten produktiven GraphQL-APIs mit Cookie-Sessions ist die Kombination aus Content-Type-Prüfung und SameSite-Cookies der pragmatische Basisschutz, ergänzt um explizite CSRF-Tokens für besonders sensible Mutations wie Zahlungs- oder E-Mail-Änderungen.
Mironsoft
GraphQL-Sicherheit, Magento-Integrationen und API-Härtung
Ist eure GraphQL-API gegen CSRF wirklich abgesichert?
Wir prüfen euren GraphQL-Endpunkt auf Content-Type-Validierung, Cookie-Konfiguration und CSRF-Token-Handling, speziell im Zusammenspiel mit Magento-Warenkorb-Sessions.
Security-Audit
Prüfung des GraphQL-Endpunkts auf CSRF-Angriffsvektoren und Cookie-Konfiguration
Absicherung
Content-Type-Checks, SameSite-Cookies und CSRF-Tokens produktiv einrichten
Magento-Integration
CSRF-Schutz speziell für Warenkorb- und Customer-Mutations in Magento GraphQL
10. Zusammenfassung
CSRF-Schutz für GraphQL-Endpunkte unterscheidet sich fundamental von klassischem REST-Schutz, weil ein einziger POST-Endpunkt sämtliche Queries und Mutations bündelt und die üblichen REST-Annahmen über sichere GET-Requests und getrennte Routen nicht mehr greifen. Sobald eine API mit Cookie-basierten Sessions arbeitet, ist explizite Absicherung Pflicht, nicht optional, da Angreifer sonst über automatisch abgeschickte Formulare state-verändernde Mutations auslösen können, ohne dass der Nutzer etwas bemerkt.
Die robusteste Kombination besteht aus strikter Content-Type-Prüfung, die nur application/json akzeptiert, korrekt konfigurierten SameSite-Cookies und CSRF-Tokens für besonders sensible Mutations. Wer komplett auf Bearer-Token-Authentifizierung umsteigen kann, umgeht das CSRF-Problem strukturell, muss dafür aber das Risiko von clientseitig gespeicherten Tokens gegen XSS absichern. Für Magento-GraphQL-Setups mit Warenkorb-Cookies bleibt eine eigene Middleware auf dem GraphQL-Controller der zuverlässigste Weg zu vollständigem CSRF-Schutz für GraphQL.
CSRF-Schutz für GraphQL-Endpunkte — Das Wichtigste auf einen Blick
Einziger Endpunkt, neue Risiken
Alle Operationen laufen über eine URL, klassische REST-Annahmen über sichere GET-Requests greifen nicht mehr.
Content-Type als Basisschutz
Nur application/json akzeptieren erzwingt einen CORS-Preflight und blockiert naive Formular-Angriffe.
SameSite und CSRF-Tokens
SameSite=Lax als zweite Schicht, CSRF-Tokens für besonders sensible Mutations wie Zahlungsänderungen.
Bearer-Token als strukturelle Lösung
Kein Cookie, kein CSRF-Risiko, dafür XSS-Absicherung für clientseitig gespeicherte Tokens nötig.