Opt-in, granular und wirklich blockierend statt nur ausgeblendet
Ein Cookie-Banner, der Tracking-Skripte weiterlaufen lässt, obwohl der Nutzer noch nicht eingewilligt hat, erfüllt weder ePrivacy noch DSGVO noch TTDSG. Dieser Artikel zeigt, wie Opt-in-Consent granular pro Zweck funktioniert, wie Skripte technisch bis zur Einwilligung blockiert werden, wie Google Consent Mode v2 und CMP-Plattformen korrekt eingebunden werden und wie man die eigene Implementierung im Network-Tab tatsächlich überprüft.
Inhaltsverzeichnis
- 1. Warum Cookie-Consent opt-in und granular sein muss
- 2. Granulare Einwilligung: Zwecke, Kategorien und Vendoren
- 3. Das häufigste Anti-Pattern: Banner ausblenden, Tracker feuert trotzdem
- 4. Technisches Script-Gating mit type="text/plain"
- 5. Tag-Manager-Consent-Gating in der Praxis
- 6. CMP-Integrationsmuster: IAB TCF v2.2 und Custom-Lösungen
- 7. Google Consent Mode v2 im Detail
- 8. Magento- und Hyvä-spezifisches Cookie-Handling
- 9. Technisches Auditing: verifizieren, dass Consent wirklich blockiert
- 10. Zusammenfassung
- 11. FAQ
1. Warum Cookie-Consent opt-in und granular sein muss
Nach § 25 TTDSG und Art. 6 DSGVO ist die Einwilligung für nicht technisch notwendige Cookies und Tracking-Skripte ausdrücklich als Opt-in zu gestalten. Der Europäische Gerichtshof hat im Planet49-Urteil klargestellt, dass vorangehakte Checkboxen keine wirksame Einwilligung darstellen, weil eine aktive Handlung des Nutzers fehlt. Wer ein Consent-Banner mit vorausgewählten Kategorien oder einem versteckten "Ablehnen"-Link ausliefert, verstößt unabhängig von der technischen Umsetzung bereits auf der rechtlichen Ebene gegen geltendes Recht. Das betrifft nicht nur Werbe-Tracker, sondern jedes Skript, das Daten außerhalb der technischen Notwendigkeit verarbeitet.
Technisch bedeutet das: Der Ausgangszustand jeder Kategorie außer "Notwendig" muss deaktiviert sein, bis der Nutzer aktiv zustimmt. Ein Banner, das nur eine Bestätigungsfläche "OK" anbietet und im Hintergrund bereits alle Kategorien als aktiv führt, ist rechtlich unwirksam, selbst wenn optisch kein Haken zu sehen ist. Die Freiwilligkeit der Einwilligung erfordert außerdem, dass Ablehnen genauso einfach erreichbar ist wie Akzeptieren, ohne Umweg über ein Untermenü oder mehrere Klicks.
2. Granulare Einwilligung: Zwecke, Kategorien und Vendoren
Ein rechtskonformes Consent-Banner unterscheidet mindestens zwischen den Kategorien "Notwendig", "Statistik", "Marketing" und "Externe Medien", wobei jede Kategorie unabhängig von den anderen aktiviert oder deaktiviert werden kann. Eine reine "Alle akzeptieren"-Fläche ohne granulare Auswahlmöglichkeit reicht nicht aus, sobald mehr als ein Verarbeitungszweck betroffen ist. Innerhalb jeder Kategorie sollten außerdem die konkreten Anbieter aufgelistet werden, etwa Google Analytics innerhalb von Statistik oder Meta Pixel innerhalb von Marketing, damit der Nutzer nachvollziehen kann, wem er welche Daten anvertraut.
Technisch lässt sich das sauber mit einer Alpine.js-Komponente umsetzen, die den Consent-Status pro Kategorie in einem reaktiven Objekt hält und bei jeder Änderung ein Event auslöst, auf das Skript-Loader reagieren. Wichtig ist, dass der Zustand nicht nur im Speicher existiert, sondern konsistent in einem First-Party-Cookie oder LocalStorage mit Zeitstempel und Versionsnummer der Consent-Konfiguration abgelegt wird, damit spätere Änderungen an der Kategorienliste eine erneute Abfrage auslösen.
<!-- Alpine.js consent banner with per-category granular toggles -->
<div
x-data="{
open: !localStorage.getItem('consent_v1'),
prefs: { necessary: true, statistics: false, marketing: false, external_media: false },
save(acceptAll) {
if (acceptAll) {
this.prefs.statistics = true;
this.prefs.marketing = true;
this.prefs.external_media = true;
}
const payload = { ...this.prefs, ts: Date.now(), version: 1 };
localStorage.setItem('consent_v1', JSON.stringify(payload));
// Notify script loaders, do not just hide the banner
window.dispatchEvent(new CustomEvent('consent:updated', { detail: payload }));
this.open = false;
}
}"
x-show="open"
class="fixed inset-x-0 bottom-0 z-50 bg-white border-t border-slate-200 p-6"
>
<fieldset class="grid grid-cols-2 gap-3 mb-4 text-sm">
<label class="flex items-center gap-2 opacity-50">
<input type="checkbox" checked disabled>
Necessary
</label>
<label class="flex items-center gap-2">
<input type="checkbox" x-model="prefs.statistics">
Statistics (Google Analytics)
</label>
<label class="flex items-center gap-2">
<input type="checkbox" x-model="prefs.marketing">
Marketing (Meta Pixel)
</label>
<label class="flex items-center gap-2">
<input type="checkbox" x-model="prefs.external_media">
External media (YouTube)
</label>
</fieldset>
<div class="flex gap-3">
<button @click="save(false)">Save selection</button>
<button @click="save(true)">Accept all</button>
<button @click="prefs = { necessary: true, statistics: false, marketing: false, external_media: false }; save(false)">Reject all</button>
</div>
</div>
3. Das häufigste Anti-Pattern: Banner ausblenden, Tracker feuert trotzdem
Der mit Abstand häufigste Implementierungsfehler ist visuell korrekt und technisch komplett wirkungslos: Das Consent-Banner wird per CSS ausgeblendet, sobald der Nutzer eine Auswahl trifft, während sämtliche Tracking-Skripte längst regulär im <head> geladen wurden und ab dem ersten Seitenaufruf Daten senden. Für den Nutzer sieht die Seite compliant aus, technisch hat aber kein einziges Skript auf die Einwilligung gewartet. Dieses Muster taucht überraschend oft in Custom-Implementierungen auf, die ein Consent-Banner als reines UI-Feature ohne Anbindung an die tatsächliche Skript-Ladelogik behandeln.
Der Unterschied zwischen einem UI-Consent-Banner und einem technisch wirksamen Consent-Layer liegt darin, ob das Laden und Ausführen von Skripten selbst an den Consent-Status gekoppelt ist. Ein korrekt implementiertes System verhindert, dass der Browser überhaupt eine Netzwerkanfrage an einen Tracking-Endpunkt stellt, bevor die entsprechende Kategorie aktiv ist. Das lässt sich nur erreichen, wenn Skripte gar nicht erst als ausführbare <script src="...">-Tags im initialen HTML stehen, sondern als deaktivierte Platzhalter, die erst nach erteilter Einwilligung aktiviert werden.
4. Technisches Script-Gating mit type="text/plain"
Das etablierte Pattern zum tatsächlichen Blockieren von Skripten funktioniert über den type-Wert des <script>-Tags. Browser führen nur Skripte mit einem bekannten JavaScript-MIME-Type aus, etwa text/javascript oder ganz ohne type-Attribut. Setzt man stattdessen type="text/plain" und ein data-consent-category-Attribut, wird das Skript vom Browser als reiner Text behandelt und nicht ausgeführt, obwohl es syntaktisch vollständig im DOM vorhanden ist. Erst ein kleiner JavaScript-Loader, der nach erteilter Einwilligung läuft, durchsucht das DOM nach diesen deaktivierten Tags, klont sie mit korrektem type und hängt sie neu ein, wodurch der Browser sie jetzt tatsächlich ausführt.
Dieses Pattern hat den entscheidenden Vorteil, dass es unabhängig von einem Tag-Manager funktioniert und auch Inline-Skripte zuverlässig blockiert, nicht nur extern eingebundene Dateien. Wichtig ist, das Klonen korrekt zu implementieren: Ein einfaches Ändern des type-Attributs am bestehenden Node reicht nicht, weil Browser ein bereits geparstes Script-Element nicht nachträglich ausführen. Der Node muss neu erzeugt und ins DOM eingefügt werden.
// Scripts stay inert until consent is granted for their category
// <script type="text/plain" data-consent-category="statistics" data-src="/js/ga.js"></script>
function activateConsentedScripts(grantedCategories) {
document.querySelectorAll('script[type="text/plain"][data-consent-category]').forEach((placeholder) => {
const category = placeholder.getAttribute('data-consent-category');
if (!grantedCategories.includes(category)) {
return; // stays blocked, no network request is ever triggered
}
// Cloning is required: mutating type on the existing node does not execute it
const activated = document.createElement('script');
for (const attr of placeholder.attributes) {
if (attr.name === 'type' || attr.name === 'data-consent-category') continue;
activated.setAttribute(attr.name, attr.value);
}
const src = placeholder.getAttribute('data-src');
if (src) {
activated.src = src;
} else {
activated.textContent = placeholder.textContent;
}
placeholder.replaceWith(activated);
});
}
window.addEventListener('consent:updated', (event) => {
const granted = Object.entries(event.detail)
.filter(([key, value]) => value === true)
.map(([key]) => key);
activateConsentedScripts(granted);
});
5. Tag-Manager-Consent-Gating in der Praxis
Wer einen Tag-Manager wie Google Tag Manager einsetzt, sollte das Blockieren nicht dem Tag-Manager selbst überlassen, sondern die Consent-Prüfung explizit auf Ebene jedes einzelnen Tags konfigurieren. Der Container-Snippet darf zwar früh laden, weil er selbst keine Tracking-Daten sendet, aber jedes einzelne Tag innerhalb des Containers muss über eingebaute Consent-Einstellungen an die jeweilige Kategorie gekoppelt sein, statt sich auf eine rein visuelle Bannersteuerung zu verlassen, die außerhalb des Tag-Managers passiert.
In der Praxis bedeutet das, für jedes Tag im Tag-Manager explizit "Zusätzliche Consent-Prüfungen" zu konfigurieren und die eingebauten Consent-Typen wie ad_storage, analytics_storage und ad_user_data zu nutzen, statt eigene Custom-Trigger für Consent zu bauen, die leicht inkonsistent zur tatsächlichen Consent-Mode-Implementierung werden. Der große Vorteil der eingebauten Consent-Checks: Sie greifen automatisch, sobald Consent Mode korrekt initialisiert ist, und verhindern die Ausführung von Tags selbst dann, wenn ein Entwickler versehentlich vergisst, einen individuellen Trigger zu setzen.
6. CMP-Integrationsmuster: IAB TCF v2.2 und Custom-Lösungen
Für Shops mit Werbepartnern im programmatischen Anzeigengeschäft ist eine Consent-Management-Plattform (CMP) nach dem IAB Transparency and Consent Framework (TCF) v2.2 häufig Pflicht, weil Werbenetzwerke den standardisierten Consent-String über die globale __tcfapi()-Funktion abfragen, bevor sie Gebote abgeben. Eine TCF-konforme CMP kodiert die granulare Einwilligung pro Zweck und Vendor in einen Base64-String, der plattformübergreifend zwischen Publisher, Werbenetzwerk und Demand-Side-Plattform ausgetauscht wird. Eigenentwickelte Consent-Lösungen ohne TCF-Anbindung funktionieren zwar für eigene First-Party-Skripte, scheitern aber häufig an der fehlenden Interoperabilität mit Drittanbieter-Werbenetzwerken.
Für reine First-Party-Anwendungsfälle ohne programmatische Werbung ist eine schlanke Custom-CMP oft ausreichend, solange sie die gleichen technischen Grundprinzipien erfüllt: granulare Kategorien, wirksame Skriptblockade, Protokollierung der Einwilligung mit Zeitstempel und eine leicht zugängliche Widerrufsmöglichkeit. Entscheidend bei der Wahl zwischen Eigenentwicklung und fertiger CMP ist weniger die Optik des Banners als die Frage, ob die Integration tatsächlich jedes einzelne Skript technisch an den Consent-Status koppelt.
7. Google Consent Mode v2 im Detail
Google Consent Mode v2 ist kein Ersatz für technisches Script-Blocking, sondern ein zusätzliches Signalprotokoll, das Google-Tags wie Google Ads und Google Analytics mitteilt, welchen Consent-Status ein Nutzer gesetzt hat. Seit März 2024 ist Consent Mode v2 für Werbekunden im Europäischen Wirtschaftsraum verpflichtend, sobald Google-Tags Werbedaten verarbeiten. Zentral sind zwei zusätzliche Parameter, ad_user_data und ad_personalization, die neben den bestehenden ad_storage und analytics_storage gesetzt werden müssen.
Das korrekte Implementierungsmuster besteht aus zwei Aufrufen: Ein gtag('consent', 'default', ...)-Aufruf setzt vor dem Laden jeglicher Google-Tags alle Consent-Typen auf denied, sodass Google-Tags im sogenannten Cookieless-Ping-Modus laufen und nur anonymisierte Modellierungsdaten senden. Nach einer Nutzerentscheidung aktualisiert ein gtag('consent', 'update', ...)-Aufruf die jeweiligen Werte auf granted. Wichtig: Consent Mode ersetzt nicht das Blockieren der Skripte selbst, es steuert nur das Verhalten bereits geladener Google-Tags, weshalb beide Mechanismen gemeinsam eingesetzt werden müssen.
// Load BEFORE any Google tag (gtag.js, GTM container, Ads, Analytics)
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
// Default: everything denied until the user decides
gtag('consent', 'default', {
ad_storage: 'denied',
analytics_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
wait_for_update: 500 // ms to wait for the banner before firing pings
});
// After the banner emits a decision, update the granted categories
window.addEventListener('consent:updated', (event) => {
gtag('consent', 'update', {
ad_storage: event.detail.marketing ? 'granted' : 'denied',
analytics_storage: event.detail.statistics ? 'granted' : 'denied',
ad_user_data: event.detail.marketing ? 'granted' : 'denied',
ad_personalization: event.detail.marketing ? 'granted' : 'denied'
});
});
8. Magento- und Hyvä-spezifisches Cookie-Handling
In Magento 2 werden Drittanbieter-Skripte üblicherweise über Layout-XML in Magento_Theme::page/js/*.phtml-Templates oder per <head>-Block eingebunden, oft direkt aus Erweiterungen für Tracking, Chat-Widgets oder Marketing-Tools. Jede dieser Einbindungen muss individuell geprüft werden, ob sie das Script-Gating-Pattern respektiert oder als klassisches, sofort ladendes <script src> im Head landet. Hyvä-Themes bringen dabei den Vorteil eines schlanken, gut überschaubaren Layout-Baums mit, in dem sich Skript-Injektionspunkte deutlich leichter auffinden lassen als in klassischen Magento-Themes mit Knockout.js-Komponenten und RequireJS-Modulen.
Praktisch bewährt sich, ein eigenes Modul mit einem zentralen Block für alle Consent-abhängigen Skripte zu bauen, das per Layout-XML gezielt in default.xml eingebunden wird und intern die deaktivierten type="text/plain"-Tags mit den jeweiligen Kategorien rendert. Magentos eigenes Cookie-Consent-Modul (Magento_Cookie) deckt nur eine einfache Ja/Nein-Bestätigung ab und erfüllt weder Granularität noch technische Blockade, weshalb es für produktive Shops durch eine eigene CMP-Integration ersetzt werden sollte.
<!-- app/design/frontend/Mironsoft/default/Magento_Theme/layout/default.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<!-- Remove any module block that injects a synchronous tracking <script src> -->
<referenceBlock name="head.additional" remove="true"/>
<referenceContainer name="before.body.end">
<!-- Central consent-gated script block, renders type="text/plain" placeholders -->
<block class="Mironsoft\CookieConsent\Block\ConsentScripts"
name="mironsoft.consent.scripts"
template="Mironsoft_CookieConsent::consent-scripts.phtml"/>
<block class="Mironsoft\CookieConsent\Block\ConsentBanner"
name="mironsoft.consent.banner"
template="Mironsoft_CookieConsent::banner.phtml"/>
</referenceContainer>
</body>
</page>
9. Technisches Auditing: verifizieren, dass Consent wirklich blockiert
Der zuverlässigste Weg, eine Consent-Implementierung zu prüfen, ist der Network-Tab der Browser-Devtools bei einem vollständig frischen Browserprofil ohne bestehende Cookies. Direkt nach dem ersten Seitenaufruf, vor jeder Interaktion mit dem Banner, dürfen ausschließlich Anfragen an technisch notwendige Endpunkte sichtbar sein. Jede Anfrage an google-analytics.com, doubleclick.net, facebook.com/tr oder vergleichbare Tracking-Domains vor einer aktiven Einwilligung ist ein eindeutiger Nachweis, dass die Implementierung nicht funktioniert, unabhängig davon, wie das Banner aussieht.
Für wiederholbare Prüfungen eignen sich automatisierte Consent-Scanner oder ein eigenes Headless-Browser-Skript mit Playwright, das die Netzwerkanfragen vor und nach jeder Consent-Kategorie protokolliert und mit der erwarteten Kategorie abgleicht. Solche Scans lassen sich in die CI-Pipeline integrieren und schlagen automatisch Alarm, wenn ein Deployment neue, ungeprüfte Skripte einführt.
#!/usr/bin/env bash
# audit-consent.sh - fail CI if trackers fire before consent
set -euo pipefail
URL="${1:?Usage: audit-consent.sh <url>}"
TRACKER_PATTERN='google-analytics\.com|doubleclick\.net|facebook\.com/tr|hotjar\.com'
echo "[INFO] Recording network requests on a clean profile before any interaction..."
node scripts/record-requests.mjs "$URL" --no-cookies --no-interaction > /tmp/pre-consent.log
if grep -qE "$TRACKER_PATTERN" /tmp/pre-consent.log; then
echo "[FAIL] Tracking requests fired before consent was granted:" >&2
grep -E "$TRACKER_PATTERN" /tmp/pre-consent.log >&2
exit 1
fi
echo "[OK] No tracking requests detected before consent."
| Prüfpunkt | Nicht-konform | Korrekt implementiert | Auswirkung |
|---|---|---|---|
| Script-Einbindung | <script src="ga.js"> direkt im Head | type="text/plain" + Consent-Callback | Tracker feuert nicht vor Consent |
| Checkbox-Zustand | Alle Kategorien vorangehakt | Alle Kategorien standardmäßig aus | Opt-in statt Opt-out |
| "Alle ablehnen" | Fehlt oder nur im Untermenü | Gleichwertig sichtbar wie "Akzeptieren" | Echte Wahlfreiheit |
| Consent-Speicherung | Nur im Frontend-State, kein Log | Zeitgestempelt, versioniert, nachweisbar | Nachweisbarkeit bei Prüfung |
| Google-Tags | Feuern unabhängig vom Consent-Status | Consent Mode v2 mit default denied | Kein Rechtsverstoß, keine stillen Verstöße |
Die Tabelle zeigt den Unterschied zwischen einer rein visuellen und einer technisch wirksamen Consent-Implementierung. Wer alle fünf Prüfpunkte konsequent umsetzt, hat ein Consent-System, das sowohl vor Gericht als auch im Network-Tab standhält.
Mironsoft
Cookie-Consent, Datenschutz und technische DSGVO-Audits für Magento-Shops
Cookie-Consent, der wirklich blockiert?
Wir prüfen eure bestehende Consent-Implementierung im Network-Tab, decken stillschweigend feuernde Tracker auf und bauen echtes Script-Gating mit Google Consent Mode v2 und CMP-Anbindung für euren Magento- oder Hyvä-Shop.
Consent-Audit
Network-Tab-Analyse und automatisierte Consent-Scans vor und nach Einwilligung
Script-Gating
type="text/plain"-Pattern und Tag-Manager-Consent-Checks korrekt verdrahten
CMP-Integration
Google Consent Mode v2, IAB TCF v2.2 und Magento/Hyvä-Layout-XML
10. Zusammenfassung
Ein rechtskonformer Cookie-Consent ist mehr als ein hübsches Banner: Er verlangt Opt-in statt vorangehakter Kästchen, granulare Kategorien statt einer einzigen Alles-oder-nichts-Fläche und vor allem echte technische Blockade statt eines rein visuellen UI-Elements. Das type="text/plain"-Pattern verhindert, dass Skripte ausgeführt werden, bevor die passende Kategorie aktiv ist, während Tag-Manager-Consent-Checks dasselbe Prinzip auf Ebene einzelner Tags durchsetzen. Google Consent Mode v2 ergänzt das um ein Signalprotokoll für Google-eigene Tags, ersetzt aber nicht das eigentliche Blockieren.
Der einzige verlässliche Nachweis, dass eine Implementierung funktioniert, ist die technische Prüfung: ein frisches Browserprofil, der Network-Tab und im Idealfall ein automatisierter Scan in der CI-Pipeline, der vor jedem Deployment sicherstellt, dass keine neuen Tracker unbemerkt vor Einwilligung feuern. Wer diese Prüfung regelmäßig durchführt, schließt die Lücke zwischen einem Banner, das compliant aussieht, und einer Implementierung, die es tatsächlich ist.
Cookie-Consent technisch sauber umsetzen - Das Wichtigste auf einen Blick
Opt-in statt Opt-out
Vorangehakte Checkboxen sind seit dem Planet49-Urteil unwirksam. Jede Kategorie außer "Notwendig" startet deaktiviert.
Granularität
Zweck- und vendorgenaue Steuerung statt einer einzigen Alles-oder-nichts-Fläche für alle Kategorien.
Echtes Script-Gating
type="text/plain" plus Consent-Callback statt reiner Banner-CSS-Steuerung, die Skripte weiterlaufen lässt.
Consent Mode v2 + Audit
default denied, update granted, plus regelmäßige Network-Tab-Verifikation vor jedem Release.