sicher erweitern
Eigene Felder in den Customer-Typen integrieren, Resolver mit Token-Validierung absichern, Extension Attributes korrekt exponieren und sensible Daten auf Feldebene schützen – alles in einem durchgängigen Praxisbeitrag.
Inhaltsverzeichnis
- 1. Warum Customer-Erweiterungen in GraphQL heikel sind
- 2. Das Customer-Schema in Magento verstehen
- 3. Extension Attributes im Schema sauber exponieren
- 4. Den Resolver bauen: Delegation statt Logik
- 5. Token-Validierung und Kontext-Prüfung im Resolver
- 6. Feldlevel-Schutz: was nie im Schema stehen sollte
- 7. Falsch gegen Richtig: typische Sicherheitslücken
- 8. Integrationstests für abgesicherte Customer-Queries
- 9. Zusammenfassung
- 10. Vergleich: Absicherungsstrategien auf einen Blick
- 11. FAQ
1. Warum Customer-Erweiterungen in GraphQL heikel sind
Kundendaten gehören zu den schützenswertesten Informationen in einem E-Commerce-System. Wer das Magento-Customer-Schema um eigene Felder erweitert, tut das meist mit gutem Grund: Bonuspunkte, Loyalitätsstufen, Präferenzen oder interne Segmentierungsmerkmale sollen im Headless-Frontend verfügbar sein. Der Weg dahin ist technisch überschaubar – die Sicherheitsdimension wird aber oft unterschätzt. Ein Resolver, der keine Token-Prüfung durchführt, macht aus einem harmlosen Feld sofort einen Datenleck-Vektor.
In Magento hängt die GraphQL-Sicherheit an einem zentralen Mechanismus: dem Customer-Context, der über den Bearer-Token im Authorization-Header befüllt wird. Jeder Resolver, der kundenbezogene Daten zurückgibt, muss diesen Kontext prüfen – sonst können unauthentifizierte Anfragen Daten anderer Kunden abrufen, wenn der Resolver keine explizite Zugangskontrolle implementiert. Dieser Artikel zeigt, wie man das Schema sauber erweitert, den Resolver korrekt aufbaut und dabei keine Sicherheitslücken öffnet.
2. Das Customer-Schema in Magento verstehen
Magento definiert den Customer-Typ in Magento_CustomerGraphQl/etc/schema.graphqls. Das Schema enthält bereits Standardfelder wie firstname, lastname, email und addresses. Die customer-Query ist mit einem Autorisierungs-Attribut versehen: Magento prüft intern, ob ein gültiger Token vorhanden ist, bevor der Resolver aufgerufen wird. Eigene Erweiterungen müssen auf diesen Typ aufsetzen – über extend type Customer in einer eigenen schema.graphqls-Datei im Modulverzeichnis.
Wichtig ist, zu verstehen, dass das bloße Hinzufügen eines Feldes zu Customer noch keine Sicherheit garantiert. Die eigentliche Absicherung passiert im Resolver. Magento liefert zwar den Kontext mit, aber ob der Resolver diesen Kontext auch auswertet, liegt vollständig beim Entwickler. Ein Feld, das intern auf eine EAV-Tabelle zeigt, und ein Resolver, der keine Kontext-Prüfung macht, sind eine Kombination, die in der Produktion zu Problemen führt.
# app/code/Vendor/CustomerExtension/etc/schema.graphqls
# Extend the native Customer type with a loyalty field
extend type Customer {
loyalty_level: String @resolver(class: "Vendor\\CustomerExtension\\Model\\Resolver\\LoyaltyLevel")
bonus_points: Int @resolver(class: "Vendor\\CustomerExtension\\Model\\Resolver\\BonusPoints")
internal_segment: String @resolver(class: "Vendor\\CustomerExtension\\Model\\Resolver\\InternalSegment")
}
3. Extension Attributes im Schema sauber exponieren
Extension Attributes sind der Magento-eigene Mechanismus, um Datenmodelle ohne Core-Änderungen zu erweitern. Im GraphQL-Kontext bedeutet das: Das EAV-Attribut oder das Custom Attribute liegt auf dem CustomerInterface, der Resolver muss es aus der richtigen Quelle lesen. Der häufige Fehler ist, direkt auf den $value-Array im Resolver zuzugreifen und zu hoffen, dass das gewünschte Attribut dort schon vorhanden ist. Tatsächlich ist der $value-Array in Magento GraphQL der aufgelöste Parent-Wert – bei Customer-Feldern enthält er die Daten des Customer-Objekts, aber nicht automatisch alle Extension Attributes.
Die saubere Lösung ist, im Resolver auf das CustomerInterface-Objekt zuzugreifen, indem man die entity_id aus dem $value-Array entnimmt und damit das vollständige Customer-Modell über ein Repository lädt. Das garantiert, dass Extension Attributes korrekt geladen werden – inklusive etwaiger Plugin-Magie, die beim Laden greift. Wer aus Performance-Gründen mehrfaches Laden vermeiden will, kann ein Request-scoped Cache-Layer mit einer eigenen Identity-Map implementieren.
4. Den Resolver bauen: Delegation statt Logik
Ein guter GraphQL-Resolver in Magento ist dünn. Er nimmt Argumente entgegen, prüft den Kontext, delegiert die eigentliche Arbeit an einen Service oder ein Repository und gibt das Ergebnis zurück. Geschäftslogik gehört nicht in den Resolver, sondern in einen Service, der unabhängig von GraphQL testbar ist. Das gilt für Customer-Resolver genauso wie für Produkt-Resolver: Der Resolver ist der Übersetzer zwischen dem GraphQL-Layer und der Fachlogik, kein Behälter für beides.
# Query to fetch loyalty data for the authenticated customer
query GetCustomerLoyalty {
customer {
firstname
email
loyalty_level
bonus_points
}
}
# Mutation to redeem bonus points
mutation RedeemPoints($points: Int!) {
redeemBonusPoints(points: $points) {
success
remaining_points
message
}
}
5. Token-Validierung und Kontext-Prüfung im Resolver
Magento stellt im Resolver-Kontext ein UserContextInterface zur Verfügung. Über $context->getUserContext()->getUserType() kann der Resolver prüfen, ob der anfragende Nutzer ein authentifizierter Kunde ist. Der User-Type UserContextInterface::USER_TYPE_CUSTOMER signalisiert, dass ein gültiger Token vorhanden ist und zu einem echten Kunden gehört. Ist der User-Type ein Gast oder ein Admin, sollte der Resolver mit einer GraphQlAuthorizationException antworten, nicht mit einem leeren Ergebnis. Leere Ergebnisse verbergen Fehler; Exceptions machen Sicherheitsprobleme sichtbar.
Zusätzlich zur User-Type-Prüfung ist es wichtig, die Customer-ID aus dem Kontext zu entnehmen und sicherzustellen, dass der Resolver nur Daten des anfragenden Kunden zurückgibt – nicht die eines anderen. Bei Customer-Feldern auf dem Customer-Typ passiert das in Magento implizit, weil die customer-Query immer den eingeloggten Kunden zurückgibt. Bei Custom-Queries, die eine Customer-ID als Argument entgegennehmen, ist die explizite Prüfung Pflicht.
# Correct: customer query always returns the authenticated customer's data
query MyAccount {
customer {
id
email
loyalty_level
bonus_points
}
}
# Wrong: exposing a query that accepts arbitrary customer IDs without auth check
# query GetAnyCustomer($id: Int!) {
# customerById(id: $id) { email bonus_points }
# }
# — This would allow any caller to read any customer's data
6. Feldlevel-Schutz: was nie im Schema stehen sollte
Nicht alles, was technisch als Extension Attribute verfügbar ist, gehört in das GraphQL-Schema. Interne Scoring-Werte, Betrugserkennungsmerkmale, Roh-Passwort-Hashes oder administrative Flags sind Beispiele für Felder, die im Headless-Frontend keinen Platz haben. Die GraphQL-Introspektion ermöglicht es jedem Client, das vollständige Schema abzufragen – was im Schema ist, ist für jeden sichtbar, der Introspektion aktivieren kann. Felder, die man nicht exponieren möchte, dürfen im Schema schlicht nicht erscheinen.
Eine zweite Kategorie sind Felder, die zwar im Schema stehen dürfen, aber nur unter bestimmten Bedingungen Daten zurückgeben sollen. Beispiel: Ein loyalty_level soll nur für Kunden sichtbar sein, die am Bonusprogramm teilnehmen. Der Resolver prüft diese Bedingung und gibt null zurück, wenn sie nicht erfüllt ist. Das ist sauberer als das Feld komplett aus dem Schema zu entfernen, weil Frontend-Entwickler so wissen, dass das Feld existiert, aber situationsabhängig ist.
7. Falsch gegen Richtig: typische Sicherheitslücken
Der häufigste Fehler bei Customer-Resolver-Erweiterungen ist das Weglassen der Kontext-Prüfung. Ein Resolver, der nur den $value-Array auswertet und keinerlei Auth-Check durchführt, gibt Daten zurück, sobald der Typ aufgelöst wird – unabhängig davon, ob ein gültiger Token vorhanden ist. In Magento schützt zwar die customer-Query selbst durch ein Authorization-Attribut, aber custom Queries, die Customer-Daten über andere Einstiegspunkte exponieren, können diesen Schutz umgehen.
| Szenario | Falsch | Richtig | Risiko |
|---|---|---|---|
| Auth-Prüfung | Kein Kontext-Check im Resolver | getUserType() prüfen, Exception werfen | Datenleck für unauthentifizierte Calls |
| Customer-ID | ID aus Argument unkontrolliert nutzen | ID aus Kontext entnehmen, Argument ignorieren | IDOR – Zugriff auf fremde Kundendaten |
| Interne Felder | Fraud-Score ins Schema aufnehmen | Interne Felder nicht im Schema exponieren | Introspection gibt interne Logik preis |
| Fehlerbehandlung | null zurückgeben bei Auth-Fehler | GraphQlAuthorizationException werfen | Fehler bleibt unsichtbar, Client merkt nichts |
| Resolver-Logik | Geschäftslogik direkt im Resolver | Service-Delegation, Resolver delegiert nur | Schwer testbar, unkontrollierbare Abhängigkeiten |
8. Integrationstests für abgesicherte Customer-Queries
Customer-Resolver-Tests in Magento sind Integrationstests, die einen echten HTTP-Request gegen den GraphQL-Endpunkt absetzen. Magento stellt hierfür die GraphQlQueryTest-Basisklasse zur Verfügung. Ein guter Testaufbau für abgesicherte Customer-Felder umfasst mindestens drei Szenarien: den erfolgreichen Abruf mit gültigem Token, den abgelehnten Abruf ohne Token mit erwartetem Fehlercode, und den Versuch, mit einem fremden Token auf Daten eines anderen Kunden zuzugreifen. Letzterer Test ist der wichtigste, wird aber am häufigsten weggelassen.
Für schnelle manuelle Verifikation im Entwicklungsalltag ist Altair oder GraphiQL mit einem gespeicherten Token im Authorization-Header die effizienteste Methode. Der Token lässt sich über die generateCustomerToken-Mutation in derselben GraphiQL-Session erzeugen und direkt in den Header-Bereich eintragen. Dieses Setup erlaubt es, neue Felder sofort nach dem Schema-Deploy zu testen, ohne ein separates Testskript schreiben zu müssen.
# Step 1: Obtain a customer token
mutation Login {
generateCustomerToken(email: "test@example.com", password: "Password1!") {
token
}
}
# Step 2: Use the token in Authorization header: Bearer <token>
# Then query the extended customer fields
query CustomerLoyaltyTest {
customer {
email
loyalty_level
bonus_points
}
}
# Step 3: Test without token — expect GraphQlAuthorizationException
# Expected response: { "errors": [{ "message": "The current customer isn't authorized." }] }
9. Zusammenfassung
Customer-Daten über Magento GraphQL zu erweitern ist technisch kein aufwändiges Vorhaben – die Kombination aus extend type Customer, eigenem Resolver und DI-Konfiguration ist in wenigen Dateien erledigt. Der eigentliche Aufwand steckt in der Sicherheitsschicht: Token-Validierung, Kontext-Prüfung, die Entscheidung welche Felder überhaupt ins Schema gehören und die Sicherstellung, dass Resolver niemals Daten fremder Kunden zurückgeben. Wer diese Aspekte bewusst durchdenkt, baut eine Erweiterung, die auch in der Produktion mit realen Kundendaten zuverlässig funktioniert.
Die wichtigste Leitfrage beim Entwurf jeder Customer-GraphQL-Erweiterung lautet: Was passiert, wenn ein Angreifer diese Query ohne Token oder mit einem fremden Token aufruft? Wenn die Antwort darauf unbefriedigend ist, fehlt entweder die Auth-Prüfung im Resolver oder das Feld gehört schlicht nicht ins Schema. Sicherheit in GraphQL ist kein nachträglicher Schritt, sondern ein Designmerkmal des Resolvers.
Customer-Daten über Magento GraphQL sicher erweitern — Das Wichtigste auf einen Blick
Schema-Erweiterung
extend type Customer in eigener schema.graphqls – nur Felder aufnehmen, die tatsächlich für das Frontend benötigt werden. Interne Attribute gehören nicht ins Schema.
Resolver-Sicherheit
getUserType() immer prüfen, GraphQlAuthorizationException bei fehlendem Token. Customer-ID immer aus dem Kontext, nie aus Query-Argumenten.
Delegation
Resolver delegieren an Services und Repositories – keine Geschäftslogik im Resolver. Testbarkeit unabhängig vom GraphQL-Layer sicherstellen.
Testing
Drei Szenarien: gültiger Token, kein Token (erwartet Exception), fremder Token (erwartet keine fremden Daten). Der dritte Test wird am häufigsten vergessen.