L'inscription de Clerk va du navigateur à Clerk, et Clerk n'a aucun hook qui s'exécute avant la création d'un utilisateur. Bot Protection décide donc juste après : la première page qu'atteint un nouveau compte l'interroge une fois, le bannit sur block et le marque comme vérifié ; d'ici là, votre application ne le laisse rien faire. C'est le modèle d'onboarding de Clerk lui-même, avec Next.js.
Le tag
Comme dans le guide Next.js, dans le layout racine, à l'intérieur de <ClerkProvider>. La fonction decide vient aussi de ce guide.
L'appel
Dans le tableau de bord de Clerk, sous Sessions, ajoutez les métadonnées publiques de l'utilisateur au token de session, pour que votre serveur lise la marque sans appel d'API :
{
"metadata": "{{user.public_metadata}}"
}
La server action qui interroge une fois et marque le compte :
// 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 page où arrivent les nouveaux comptes. Envoyez-les-y avec 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>; }
Un middleware qui y renvoie un compte non marqué. Clerk appelle cela une commodité : dans un route handler ou une server action qui dépense de l'argent ou envoie des messages, vérifiez de nouveau 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 du visiteur
La décision s'exécute dans votre server action, donc l'IP est celle qu'indique le guide Next.js. La protection anti-bot de Clerk à l'inscription peut rester active : elle protège le formulaire de Clerk, et Bot Protection le compte qu'il a créé.
Appliquer l'action
Ne lisez que action. Avec block, répondez avec la même erreur que l'action affiche quand elle échoue pour toute autre raison, pour qu'un bot n'apprenne rien. Avec challenge, utilisez la vérification que vous avez déjà (un CAPTCHA, un code par e-mail ou téléphone) ; sans aucune, continuez. Le compte commence en mode monitor, où action est toujours allow : rien ne change pour les visiteurs tant que vous n'activez pas le mode block.
Garder le request_id
Chaque réponse a un request_id. Gardez-le avec ce que l'action a créé, dans une colonne du compte ou de la commande, ou dans une ligne de log avec leur identifiant. Il désigne cette décision quand vous nous interrogez à son sujet, et vous permet de retrouver plus tard quelle réponse a laissé entrer un faux compte.
Tester sur votre machine
Lancez next dev avec vos clés de développement Clerk et ouvrez http://localhost:3000. Les pages sur localhost acceptent toujours le tag et n'apparaissent jamais comme domaine. En local, l'IP est 127.0.0.1 ou ::1, et c'est très bien. Dans Docker sur votre machine, l'app voit à la place l'adresse du bridge, comme 172.17.0.1 : la page de challenge vous ramène quand même sur localhost, puisque le tag y a vu votre page.
- Définissez
KORZEN_API_KEYdans.env.localet ouvrez la page d'inscription. Dans les outils de développement du navigateur, le cookie_kzapparaît après quelques secondes. - Inscrivez-vous. La décision apparaît sur la page Décisions du tableau de bord, sous le chemin que vous avez envoyé.
- Vérifiez le fail-open : mettez une mauvaise clé et inscrivez-vous à nouveau. L'appel répond
401, et l'inscription passe.
Erreurs fréquentes
- La marque vérifiée seulement dans le middleware. Un bot appelle directement vos routes d'API : vérifiez le claim là où cela compte.
- Pas de
user.reload()après la marque. Le token de session garde les anciennes métadonnées jusqu'à une minute, et le middleware renvoie le compte. - Interroger à chaque visite. Chaque appel compte comme une décision : la marque le limite à une fois par compte.
- Appeler
/v1/decidedepuis le navigateur. L'API key atteindrait chaque visiteur ; l'appel a sa place sur votre serveur. - Pas de timeout, ou une erreur qui bloque l'inscription. Toute réponse autre que
200, ou l'absence de réponse à temps, signifie : laissez passer le visiteur.