Better Auth gira sul tuo server, quindi la richiesta che registra un visitatore passa dal tuo codice. Un hook databaseHooks.user.create.before viene eseguito prima che Better Auth crei qualsiasi utente, dal modulo con email e password o dal callback di un provider social, e riceve gli header della richiesta: lì chiede a Bot Protection e rifiuta l'utente su block. Questa guida è per Better Auth 1.x.
Il tag
Come nella guida del tuo framework (Next.js, Express), nel layout delle tue pagine, così che la pagina di registrazione lo abbia:
<script src="https://korzen.dev/v1/tag.js" data-site-key="YOUR_SITE_KEY" async></script>
La chiamata
Il client è un solo file senza dipendenze: chiede a /v1/decide, rinuncia allo scadere del tuo timeout e lascia passare il visitatore a ogni errore. Scaricalo accanto alla tua configurazione di Better Auth e leggilo prima del commit:
curl -o lib/bot-protection.js https://korzen.dev/bot-protection/sdk/korzen.js curl -o lib/bot-protection.d.ts https://korzen.dev/bot-protection/sdk/korzen.d.ts # types, for TypeScript
Poi l'hook, e un campo sull'utente che conserva il request_id della decisione:
// lib/auth.js import { betterAuth } from "better-auth"; import { APIError } from "better-auth/api"; import { createClient, fromHeaders, ipFrom } from "./bot-protection.js"; const korzen = createClient({ apiKey: process.env.KORZEN_API_KEY, url: "https://korzen.dev", timeoutMs: 200 }); export const auth = betterAuth({ // your database, emailAndPassword, socialProviders ... user: { additionalFields: { korzenRequestId: { type: "string", required: false, input: false } }, }, databaseHooks: { user: { create: { before: async (user, ctx) => { if (!ctx?.headers) return; // a user your own code creates, outside a request const d = await korzen.decide( fromHeaders(ctx.headers, { ip: ipFrom.vercel, // your platform's: see "The visitor's IP" path: "/signup", method: ctx.request?.method ?? "POST", }), ); if (d?.action === "block") { throw new APIError("BAD_REQUEST", { message: "We could not create your account." }); } // "challenge": ask for a phone number, or another check, before the account does anything return { data: { ...user, korzenRequestId: d?.request_id ?? null } }; }, }, }, }, });
Poi npx auth@latest generate (o migrate, con l'adapter Kysely di Better Auth) una volta, così il database ha la nuova colonna.
L'IP del visitatore
L'hook gira sul tuo server, quindi l'IP è quello che dice la guida della tua piattaforma: ipFrom.vercel su Vercel, ipFrom.cloudflare se il tuo server riceve traffico solo da Cloudflare, ipFrom.lastForwarded dietro il tuo load balancer o nginx. Un header che il visitatore può scrivere lascia a un bot la scelta del proprio IP.
Applica l'azione
Leggi solo action. Su block, rispondi con lo stesso errore che l'azione mostra quando fallisce per qualsiasi altro motivo, così il bot non impara niente. Su challenge, usa la verifica che hai già (un CAPTCHA, un codice via email o telefono); se non ne hai, prosegui. L'account parte in monitor mode, dove action è sempre allow: per i visitatori non cambia niente finché non attivi il block mode.
Conserva il request_id
Ogni risposta ha un request_id. Conservalo insieme a ciò che l'azione ha creato, in una colonna dell'account o dell'ordine, o in una riga di log con il loro id. Identifica quella decisione quando ci chiedi informazioni, e ti permette di scoprire in seguito quale risposta ha fatto entrare un account falso.
Provalo sulla tua macchina
Avvia il tuo server e apri la pagina di registrazione su localhost. Le pagine su localhost accettano sempre il tag e non compaiono mai tra i domini. In locale l'IP è 127.0.0.1 o ::1, e va bene così. In Docker sulla tua macchina, l'app vede invece l'indirizzo del bridge, come 172.17.0.1: la pagina di challenge ti riporta comunque su localhost, perché il tag ha visto lì la tua pagina.
- Imposta
KORZEN_API_KEYin.enve apri la pagina di registrazione. Negli strumenti per sviluppatori del browser, il cookie_kzcompare dopo qualche secondo. - Registrati. La decisione compare nella pagina Decisions della dashboard, sotto
/signup, e il nuovo utente ha il suokorzenRequestId. - Verifica il fail-open: imposta una chiave sbagliata e registrati di nuovo. La chiamata risponde
401, e la registrazione va a buon fine.
Errori comuni
- Un
hooks.beforesolo su/sign-up/email. Le registrazioni con Google o GitHub non ci passano mai; il database hook vede ogni nuovo utente. - Chiedere anche in
session.create. Ogni chiamata conta come una decisione: qui ne basta una per account nuovo; proteggi login e checkout nei loro handler. - Chiamare
/v1/decidedal browser. L'API key arriverebbe a ogni visitatore; la chiamata va fatta dal tuo server. - Nessun timeout, o un errore che blocca la registrazione. Qualsiasi risposta diversa da
200, o nessuna risposta in tempo, significa: lascia passare il visitatore. - Una riga nel tuo log che inizia con
korzen: the visitor's IP sent to Bot Protection. Il client la scrive una volta, alla prima decisione il cui IP è della tua rete (di un load balancer, di un proxy, di un container), che Bot Protection giudica come senza IP: impostaipsull'indirizzo del visitatore (l'IP del visitatore). In Docker sulla tua macchina è l'indirizzo del bridge, e lì non fa danni.