La registrazione di Clerk va dal browser a Clerk, e Clerk non ha nessun hook che giri prima di creare un utente. Quindi Bot Protection decide subito dopo: la prima pagina che un nuovo account raggiunge lo valuta una volta, lo banna su block e lo segna come verificato; fino ad allora, la tua app non gli lascia fare niente. È lo stesso pattern di onboarding di Clerk, con Next.js.
Il tag
Come nella guida Next.js, nel root layout, dentro <ClerkProvider>. Anche la funzione decide è quella della guida.
La chiamata
Nella dashboard di Clerk, in Sessions, aggiungi i public metadata dell'utente al session token, così il tuo server legge il segno senza una chiamata API:
{
"metadata": "{{user.public_metadata}}"
}
La server action che chiede una volta e segna l'account:
// app/signup/check/actions.js "use server"; import { auth, clerkClient } from "@clerk/nextjs/server"; import { decide } from "@/lib/korzen"; // from the Next.js guide // check asks Bot Protection once about a new account, and marks it checked. export async function check() { const { userId } = await auth(); if (!userId) return "signed-out"; const client = await clerkClient(); const user = await client.users.getUser(userId); if (user.publicMetadata.korzen) return "done"; let d = null; if (Date.now() - user.createdAt < 24 * 3600_000) { // accounts made before you added Bot Protection are marked without a decision d = await decide("/signup"); if (d?.action === "block") { await client.users.banUser(userId); // ends its sessions; it cannot sign in again return "blocked"; } // "challenge": ask for a phone number, or another check, before going on } await client.users.updateUserMetadata(userId, { publicMetadata: { korzen: { request_id: d?.request_id ?? null } }, }); return "done"; }
La pagina su cui arrivano i nuovi account. Mandaceli con NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL=/signup/check:
// app/signup/check/page.js "use client"; import { useUser } from "@clerk/nextjs"; import { useRouter } from "next/navigation"; import { useEffect } from "react"; import { check } from "./actions"; export default function Check() { const { isLoaded, user } = useUser(); const router = useRouter(); useEffect(() => { if (!isLoaded) return; (async () => { await window.korzen?.ready(); if ((await check()) !== "done") return router.push("/sign-in"); await user?.reload(); // a new session token, with the mark in it router.push("/dashboard"); })(); }, [isLoaded]); return <p>One moment…</p>; }
Il middleware che rimanda lì un account non segnato. Clerk lo considera una comodità: in una route handler o server action che spende soldi o invia messaggi, controlla di nuovo sessionClaims?.metadata?.korzen.
// middleware.js (proxy.js on Next.js 16) import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server"; import { NextResponse } from "next/server"; const isOpen = createRouteMatcher(["/", "/sign-in(.*)", "/sign-up(.*)", "/signup/check"]); export default clerkMiddleware(async (auth, req) => { const { userId, sessionClaims } = await auth(); if (userId && !sessionClaims?.metadata?.korzen && !isOpen(req)) { return NextResponse.redirect(new URL("/signup/check", req.url)); } }); // keep the config.matcher your Clerk set-up already has
L'IP del visitatore
La decisione gira nella tua server action, quindi per l'IP vale quanto dice la guida Next.js. La bot protection di Clerk sulla registrazione può restare attiva: protegge il form di Clerk, e Bot Protection l'account che ne è nato.
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 next dev con le chiavi di sviluppo di Clerk e apri http://localhost:3000. 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.env.locale apri la pagina di registrazione. Negli strumenti per sviluppatori del browser, il cookie_kzcompare dopo qualche secondo. - Registrati. La decisione compare nella pagina Decisioni della dashboard, sotto il path che hai inviato.
- 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
- Il segno controllato solo nel middleware. Un bot chiama direttamente le tue API route: controlla il claim dove conta.
- Nessun
user.reload()dopo il segno. Il session token mantiene i vecchi metadata fino a un minuto, e il middleware rimanda indietro l'account. - Chiedere di nuovo a ogni visita. Ogni chiamata conta come decisione: il segno fa sì che avvenga una volta per account.
- 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.