Steht Ihre Website hinter Cloudflare, kann ein Worker auf den Routen, die Sie schützen, Bot Protection fragen, bevor die Anfrage Ihren Origin erreicht: ganz ohne Änderung an Ihrer Anwendung. Er läuft am Edge von Cloudflare, nahe an korzen.dev, das ebenfalls hinter Cloudflare steht. Auf einer statischen Website erledigt eine Cloudflare Pages Function dasselbe für eine Aktion, wie in unserem eigenen Blog.
Der Tag
Der Tag gehört trotzdem auf Ihre Seiten, in den <head>, damit das _kz-Cookie da ist, wenn der Besucher absendet:
<script src="https://korzen.dev/v1/tag.js" data-site-key="YOUR_SITE_KEY" async></script>
Der Worker
Der Client ist eine einzige Datei ohne Abhängigkeiten: Er fragt /v1/decide, gibt bei Ihrem Timeout auf und lässt den Besucher bei jedem Fehler durch. Laden Sie ihn neben Ihren Worker und lesen Sie ihn, bevor Sie ihn committen:
curl -o src/bot-protection.js https://korzen.dev/bot-protection/sdk/korzen.js
createWorker ist der ganze Worker: Er fragt zu jeder Anfrage auf seinen Routen mit Ihrem Timeout und nimmt seine Einstellungen aus der Umgebung des Workers:
// src/index.js: a Worker on the routes Bot Protection judges, in front of the cache and your origin. // wrangler.toml: // routes = [ // { pattern = "tickets.example/api/resale/*", zone_name = "tickets.example" }, // { pattern = "tickets.example/orders/*", zone_name = "tickets.example" }, // ] // [vars] // KORZEN_URL = "https://korzen.dev" // KORZEN_TIMEOUT_MS = "200" // KORZEN_REQUIRE_TOKEN = "/orders/*" // The key: npx wrangler secret put KORZEN_API_KEY import { createWorker } from "./bot-protection.js"; export default createWorker();
KORZEN_API_KEY: Ihr geheimer Schlüssel, als Worker-Secret. Ohne ihn geht jede Anfrage ungeprüft weiter, und das Log des Workers sagt es einmal.KORZEN_URL:https://korzen.dev.KORZEN_TIMEOUT_MS: wie lange eine Entscheidung dauern darf, 10 bis 1000 ms (200, wenn nicht gesetzt). Danach geht die Anfrage weiter.KORZEN_REQUIRE_TOKEN: die Pfade, deren Aktionen das Token des Tags brauchen, durch Kommas getrennt; einer, der auf*endet, ist ein Präfix.
Bei block antwortet der Worker mit 403. Bei challenge geht im block mode ein Seitenaufruf zur Challenge-Seite und kommt mit einem Token zurück, das er als Cookie _kz speichert; ein herausgeforderter Formular-POST oder API-Aufruf erhält 403. Alles andere geht weiter an den Cache von Cloudflare und Ihren Origin, mit den Headern Korzen-Request-Id und Korzen-Action, die Ihre Anwendung loggen kann. Jeder Fehler und jedes Timeout lässt die Anfrage durch: Ist Bot Protection langsam oder nicht erreichbar, läuft Ihre Website wie ohne den Worker.
Aktionen, die ein Token brauchen
Eine Aktion auf einem Pfad in KORZEN_REQUIRE_TOKEN (ihr POST, PUT, PATCH oder DELETE: ein Seitenaufruf verbraucht kein Token) geht nur mit einem Token weiter, das der Tag in diesem Browser verdient hat, einmal pro Sitzung. Ein Skript, das nie JavaScript ausführt, wird bei jedem Versuch abgewiesen, von welcher Adresse es auch kommt. Setzen Sie den Tag auf die Seite, die zur Aktion führt, und diese Seite ebenfalls auf die Routen des Workers, damit ein Mensch eine Challenge schon beim Laden sieht.
Abfragen aus dem Cache
Der Worker läuft vor dem Cache von Cloudflare: Eine Anfrage, die der Cache beantwortet, erreicht Ihren Server nie, den Worker aber schon. Legen Sie den Worker auf die Routen, die abgefragt werden (ein Bestand, ein Sitzplan), und jede Abfrage wird geprüft; die erlaubten kommen weiterhin aus dem Cache. Jede Anfrage auf den Routen des Workers ist eine Entscheidung im Volumen Ihres Plans, routen Sie also nur, was geprüft werden soll.
Was Cloudflare hinzufügt
Mit jeder Entscheidung sendet der Worker, was Cloudflare über den Besucher gemessen hat (request.cf): in jedem Plan das Netz (ASN), das Land, die HTTP-Version und die TCP-Round-Trip-Zeit zwischen Besucher und Cloudflare (keine über HTTP/3); mit Enterprise Bot Management auch den JA4-Fingerprint und die Detection-IDs. Bot Protection hält diese Round-Trip-Zeit gegen die, die der Tag auf der Seite gemessen hat: Eine Aufzeichnung aus einem anderen Netz behauptet eine Round-Trip-Zeit, die die eigene Verbindung des Besuchers nicht haben kann. Keiner dieser Werte blockiert einen Besucher allein.
Ein eigener Handler
createWorker deckt den üblichen Fall ab. Für einen Worker, der mehr tut, umschließen Sie Ihr eigenes fetch mit withKorzen; es sendet ebenso, was Cloudflare gemessen hat.
withKorzen umschließt das fetch des Workers: Es liest, was Cloudflare über den Besucher weiß, und fragt mit Ihrem Timeout. Bei block antwortet es selbst mit 403. Bei challenge geht im block mode ein Seitenaufruf zur Challenge-Seite und kommt mit einem Token zurück, das es als Cookie _kz speichert: Der Tag führt diese Sitzung fort, und ihre nächsten Entscheidungen sind allow. Alles andere geht weiter an Ihren Origin, mit der ID und der Aktion der Entscheidung in Headern, die Ihre Anwendung loggen kann:
// src/index.js: a Worker on the routes you protect, in front of your origin. // wrangler.toml: routes = [{ pattern = "shop.example/signup*", zone_name = "shop.example" }] // The key: npx wrangler secret put KORZEN_API_KEY import { createClient, ipFrom, withKorzen } from "./bot-protection.js"; let protect; // made once per isolate, with the key from env export default { fetch(request, env, ctx) { protect ??= withKorzen( async (request, env, ctx, d) => { // on to your origin, with the decision's id and action const forward = new Request(request); if (d?.request_id) forward.headers.set("Korzen-Request-Id", d.request_id); if (d?.action) forward.headers.set("Korzen-Action", d.action); return fetch(forward); }, { client: createClient({ apiKey: env.KORZEN_API_KEY, url: "https://korzen.dev", timeoutMs: 200 }), ip: ipFrom.cloudflare, // CF-Connecting-IP: Cloudflare sets it }, ); return protect(request, env, ctx); }, };
Ein Formularversand mit Challenge kann nicht zur Seite und zurück: withKorzen lehnt ihn mit 403 ab. Mit einer eigenen Prüfung am Origin übergeben Sie onChallenge: () => undefined, dann erreicht er Ihren Origin mit Korzen-Action: challenge. Legen Sie auch die Seite, die das Formular zeigt, auf die Routen des Workers, dann bekommt der Besucher die Challenge beim Laden der Seite.
Die IP des Besuchers
CF-Connecting-IP wird von Cloudflare gesetzt und kann vom Besucher nicht geschrieben werden, also hat ein Worker immer die richtige Adresse. Ihr Origin sollte trotzdem nur Traffic von Cloudflare annehmen, sonst kann ein Bot den Worker umgehen, indem er ihn direkt aufruft.
Die Header und ihre Reihenfolge
withKorzen sendet die Header, deren Werte ein Browser-Build festlegt (Accept, Accept-Encoding, die Client Hints, Sec-Fetch-*), und Accept-Language: Ein Skript, das den User-Agent eines Browsers kopiert, kopiert selten alle. Ihre Reihenfolge (header_order) sendet es nicht: request.headers listet in einem Worker die Namen sortiert, nicht so, wie sie kamen. Das Feld bleibt also weg, und die Entscheidung liest den Rest. Auf Ihrem eigenen Node-Server sendet fromNodeRequest die Reihenfolge aus rawHeaders.
Testen
npx wrangler dev führt den Worker auf Ihrem Rechner aus; setzen Sie den Schlüssel in .dev.vars als KORZEN_API_KEY=…. Dann npx wrangler deploy, und beobachten Sie die Entscheidungen auf der Seite Entscheidungen des Dashboards.
Häufige Fehler
- Ein Worker auf jeder Route: Jede Anfrage auf seinen Routen ist eine Entscheidung. Routen Sie nur, was geprüft werden soll: die Abfragen, die Sie beobachten, Registrierung, Anmeldung, Checkout.
- Ein Pfad in
KORZEN_REQUIRE_TOKEN, dessen Seiten keinen Tag haben: Dort hat kein Besucher ein Token, also wird jede Aktion abgewiesen. - Den Body der Anfrage lesen, um zu entscheiden, und dann einen bereits verbrauchten Body weiterleiten: Der Body eines Requests kann einmal gelesen werden.
- Ein Origin, der für das ganze Internet offen ist: Der Worker schützt nur die Anfragen, die über Cloudflare laufen.