De registratie van Clerk gaat van de browser naar Clerk, en Clerk heeft geen hook die draait voordat het een gebruiker aanmaakt. Daarom beslist Bot Protection direct daarna: de eerste pagina die een nieuw account bereikt vraagt er één keer naar, bant het bij block, en markeert het als gecontroleerd; tot dan laat je app het niets doen. Dit is het eigen onboarding-patroon van Clerk, met Next.js.
De tag
Zoals in de Next.js-gids, in de root layout, binnen <ClerkProvider>. De functie decide komt ook uit die gids.
De call
Voeg in het Clerk-dashboard, onder Sessions, de public metadata van de gebruiker toe aan de session token, zodat je server de markering leest zonder API-call:
{
"metadata": "{{user.public_metadata}}"
}
De server action die één keer vraagt en het account markeert:
// 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"; }
De pagina waar nieuwe accounts op landen. Stuur ze erheen met 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>; }
Middleware die een ongemarkeerd account daarheen terugstuurt. Clerk noemt dit een gemak: controleer in een route handler of server action die geld uitgeeft of berichten verstuurt sessionClaims?.metadata?.korzen opnieuw.
// 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
Het IP-adres van de bezoeker
De beslissing draait in je server action, dus het IP-adres is wat de Next.js-gids zegt. De eigen botbescherming van Clerk bij registratie kan aan blijven: die bewaakt het formulier van Clerk, en Bot Protection het account dat daaruit voortkomt.
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.
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.
Test het op je eigen machine
Draai next dev met je Clerk-development keys en open http://localhost:3000. 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.env.localen 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
- De markering alleen in middleware controleren. Een bot roept je API-routes direct aan: controleer de claim waar het ertoe doet.
- Geen
user.reload()na de markering. De session token houdt de oude metadata tot een minuut lang vast, en de middleware stuurt het account terug. - Bij elk bezoek opnieuw vragen. Elke call telt als beslissing: door de markering gebeurt het één keer per account.
/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.