Deze gids gebruikt alleen de standaardbibliotheek: net/http en één http.Client voor het proces, die zijn verbindingen openhoudt. Zijn Timeout begrenst de hele call, van verbinden tot het lezen van de body.
De tag
Zet de tag in de <head> van de layout die je registratie-, inlog- en checkoutpagina's rendert:
<script src="https://korzen.dev/v1/tag.js" data-site-key="YOUR_SITE_KEY" async></script>
Roep op de pagina, voordat het formulier wordt verstuurd, await window.korzen?.ready() aan: het meldt wat de bezoeker deed sinds de tag het laatst meldde, en is hoe dan ook binnen een paar seconden klaar. De tag
De call
De client is één bestand met alleen de standaardbibliotheek, korzen.go: hij vraagt /v1/decide, geeft op bij je timeout en laat de bezoeker door bij elke fout. Download hem in een package van je module en lees hem voordat je hem commit:
mkdir -p internal/korzen curl -o internal/korzen/korzen.go https://korzen.dev/bot-protection/sdk/korzen.go
Eén Client voor het proces: hij houdt zijn verbindingen open, en zijn Timeout begrenst de hele call. Protect vraagt vóór de handler die het account aanmaakt, en zet het antwoord in de context van het request:
var bot = &korzen.Client{APIKey: os.Getenv("KORZEN_API_KEY"), URL: "https://korzen.dev", Timeout: 200 * time.Millisecond} mux.Handle("POST /signup", bot.Protect(http.HandlerFunc(signup), korzen.Options{ IP: korzen.RemoteIP, // see "The visitor's IP" OnBlock: func(w http.ResponseWriter, r *http.Request, d korzen.Decision) bool { http.Error(w, "We could not create your account.", http.StatusBadRequest) return true }, // Your own check for challenge runs in signup: return false to go on to it. Without // OnChallenge, a challenged post is refused (403). OnChallenge: func(http.ResponseWriter, *http.Request, korzen.Decision) bool { return false }, })) func signup(w http.ResponseWriter, r *http.Request) { d, _ := korzen.FromContext(r.Context()) // no answer: the zero Decision if d.Action == korzen.Challenge { // your CAPTCHA, or an email or phone check, before going on } user, err := createAccount(r) // your own code // ... saveRequestID(user.ID, d.RequestID) // see "Keep the request_id" }
Om in een handler te vragen: d, err := bot.Decide(r.Context(), korzen.FromHTTP(r, korzen.RemoteIP(r))). Bij elke fout is d.Action allow, en err zegt waarom, voor je log. Een antwoord waarvan action niet allow, challenge of block is, telt ook als fout; de *korzen.Problem van een foutantwoord zit in err (errors.As). Om elke call te loggen, zet je OnAnswer van de client: die krijgt de status, Korzen-Request-Id en de RateLimit-headers.
Het IP-adres van de bezoeker
r.RemoteAddr is het adres van de verbinding: dat van je proxy, als je er een hebt. korzen.RemoteIP leest het: gebruik het als er niets voor je server staat. korzen.CloudflareIP leest CF-Connecting-IP, wat alleen klopt als je server uitsluitend verkeer van Cloudflare aanneemt; anders kan een bezoeker hem schrijven. Achter je eigen load balancer neemt korzen.LastForwardedIP de laatste waarde van X-Forwarded-For, die hij zelf toevoegde.
Voer de actie uit
Lees alleen action. Bij block antwoord je met dezelfde foutmelding die de actie toont als hij om een andere reden mislukt, zodat een bot niets leert. Bij challenge gebruik je de controle die je al hebt (een CAPTCHA, een code per e-mail of telefoon); heb je er geen, ga dan door. Het account begint in monitor mode, waar action altijd allow is: voor bezoekers verandert er niets tot je block mode aanzet. Of stuur de bezoeker naar onze challengepagina, op challenge.url: zie de challengepagina.
Zonder OnChallenge laat de middleware geen challenge door: het laden van een pagina (GET) gaat naar onze challengepagina, en een verstuurd formulier of een API-call wordt geweigerd met 403. Bescherm ook de pagina die het formulier toont: daar krijgt een persoon de challenge, en zijn pass laat het formulier door. Heb je een eigen controle, geef dan OnChallenge en voer hem daar uit. De middleware stuurt de persoon terug naar het opgevraagde adres, gelezen uit X-Forwarded-Host en X-Forwarded-Proto als je proxy die zet; achter een proxy die Host: localhost doorgeeft en geen van beide (standaard bij nginx), geef je PublicURL: "https://shop.example".
Bewaar de request_id
Elk antwoord heeft een request_id. Bewaar die bij wat de actie heeft aangemaakt, in een kolom van het account of de bestelling, of in een logregel met hun id. Zo weet je welke beslissing het was als je ons ernaar vraagt, en kun je later nagaan welk antwoord een nepaccount binnenliet. Blijkt het account goed of slecht, meld het dan terug: bot.Feedback(ctx, []korzen.Feedback{{RequestID: id, Outcome: "chargeback"}}, korzen.Batch{}) (zie feedback).
Test het op je eigen machine
Draai go run . en open je server op localhost. Pagina's op localhost accepteren de tag altijd en staan nooit als domein in de lijst. Lokaal is het IP-adres 127.0.0.1 of ::1, en dat is prima. In Docker op je eigen machine ziet de app in plaats daarvan het adres van de bridge, zoals 172.17.0.1: de challengepagina brengt je toch terug naar localhost, want de tag zag je pagina daar.
- Zet
KORZEN_API_KEYin de omgeving en open de registratiepagina. In de developer tools van de browser verschijnt na een paar seconden de_kz-cookie. - Registreer je. De beslissing verschijnt op de pagina Beslissingen van het dashboard, onder het pad dat je stuurde.
- Controleer fail-open: zet een verkeerde key en registreer je opnieuw. De call antwoordt met
401, en de registratie gaat door.
Veelgemaakte fouten
- Een
HTTPClientmet een nieuwehttp.Transportper call, of de standaard van de bibliotheek, die twee verbindingen openhoudt. Elke nieuwe is een nieuwe TLS-verbinding: laatHTTPClientleeg, of geef de jouwekorzen.NewTransport(), één keer voor het proces. CF-Connecting-IPlezen op een server waar Cloudflare niet voor staat. Een bot schrijft de header zelf en kiest zijn eigen IP-adres./v1/decideaanroepen vanuit de browser. De API key zou dan bij elke bezoeker terechtkomen; de call hoort op je server.- Geen timeout, of een fout die de registratie tegenhoudt. Elk ander antwoord dan
200, en geen antwoord op tijd, betekent: laat de bezoeker door. - Een regel in je log die begint met
korzen: the visitor's IP sent to Bot Protection. De client schrijft hem één keer, bij de eerste beslissing waarvan het IP van je eigen netwerk is (van een load balancer, een proxy, een container), die Bot Protection beoordeelt als zonder IP: zetOptions.IPop het adres van de bezoeker (het IP van de bezoeker). In Docker op je eigen machine is het het adres van de bridge, en daar onschuldig.