Korzen
Bot Protection

Clerk

Bot Protection mit Clerk: eine Entscheidung auf der ersten Seite eines neuen Kontos, eine Sperre bei block und eine Markierung im Session-Token, die Ihre App prüft.

Clerks Registrierung geht vom Browser zu Clerk, und Clerk hat keinen Hook, der läuft, bevor es einen Nutzer anlegt. Also entscheidet Bot Protection direkt danach: Die erste Seite, die ein neues Konto erreicht, fragt einmal danach, sperrt es bei block und markiert es als geprüft; bis dahin lässt Ihre App es nichts tun. Das ist Clerks eigenes Onboarding-Muster, mit Next.js.

Der Tag

Wie in der Next.js-Anleitung im Root-Layout, innerhalb von <ClerkProvider>. Die Funktion decide stammt ebenfalls aus dieser Anleitung.

Der Aufruf

Fügen Sie im Clerk-Dashboard unter Sessions die öffentlichen Metadaten des Nutzers zum Session-Token hinzu, damit Ihr Server die Markierung ohne API-Aufruf liest:

{
  "metadata": "{{user.public_metadata}}"
}

Die Server Action, die einmal fragt und das Konto markiert:

// 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";
}

Die Seite, auf der neue Konten landen. Schicken Sie sie mit NEXT_PUBLIC_CLERK_SIGN_UP_FORCE_REDIRECT_URL=/signup/check dorthin:

// 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 ein unmarkiertes Konto dorthin zurückschickt. Clerk nennt das eine Bequemlichkeit: In einem Route Handler oder einer Server Action, die Geld ausgibt oder Nachrichten sendet, prüfen Sie sessionClaims?.metadata?.korzen erneut.

// 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

Die IP des Besuchers

Die Entscheidung läuft in Ihrer Server Action, also ist die IP die, die die Next.js-Anleitung nennt. Clerks eigener Bot-Schutz bei der Registrierung kann eingeschaltet bleiben: Er schützt Clerks Formular, und Bot Protection das Konto, das daraus entstanden ist.

Die Aktion anwenden

Lesen Sie nur action. Bei block antworten Sie mit demselben Fehler, den die Aktion zeigt, wenn sie aus einem anderen Grund scheitert, damit ein Bot nichts lernt. Bei challenge nutzen Sie die Prüfung, die Sie schon haben (ein CAPTCHA, ein Code per E-Mail oder Telefon); ohne eine machen Sie weiter. Das Konto beginnt im Monitor-Modus, in dem action immer allow ist: Für Besucher ändert sich nichts, bis Sie den Block-Modus einschalten.

Die request_id aufbewahren

Jede Antwort hat eine request_id. Bewahren Sie sie bei dem auf, was die Aktion erstellt hat, in einer Spalte des Kontos oder der Bestellung, oder in einer Logzeile mit deren ID. Sie benennt diese Entscheidung, wenn Sie uns dazu etwas fragen, und lässt Sie später herausfinden, welche Antwort ein Fake-Konto durchgelassen hat.

Auf Ihrem Rechner testen

Führen Sie next dev mit Ihren Clerk-Entwicklungsschlüsseln aus und öffnen Sie http://localhost:3000. Seiten auf localhost akzeptieren den Tag immer und erscheinen nie als Domain. Lokal ist die IP 127.0.0.1 oder ::1, und das ist in Ordnung. Läuft die App auf Ihrem Rechner in Docker, sieht sie stattdessen die Adresse der Bridge, etwa 172.17.0.1: Die Challenge-Seite bringt Sie trotzdem zu localhost zurück, denn der Tag hat Ihre Seite dort gesehen.

  1. Setzen Sie KORZEN_API_KEY in .env.local und öffnen Sie die Registrierungsseite. In den Entwicklertools des Browsers erscheint nach ein paar Sekunden das _kz-Cookie.
  2. Registrieren Sie sich. Die Entscheidung erscheint auf der Seite Entscheidungen des Dashboards, unter dem Pfad, den Sie gesendet haben.
  3. Prüfen Sie fail-open: Setzen Sie einen falschen Schlüssel und registrieren Sie sich erneut. Der Aufruf antwortet 401, und die Registrierung geht durch.

Häufige Fehler

  • Die Markierung nur in der Middleware prüfen. Ein Bot ruft Ihre API-Routen direkt auf: Prüfen Sie den Claim dort, wo es zählt.
  • Kein user.reload() nach der Markierung. Der Session-Token behält die alten Metadaten bis zu einer Minute, und die Middleware schickt das Konto zurück.
  • Bei jedem Besuch erneut fragen. Jeder Aufruf zählt als Entscheidung: Die Markierung macht es einmal pro Konto.
  • /v1/decide aus dem Browser aufrufen. Der API key würde jeden Besucher erreichen; der Aufruf gehört auf Ihren Server.
  • Kein Timeout, oder ein Fehler, der die Registrierung stoppt. Jede andere Antwort als 200, und keine Antwort rechtzeitig, bedeutet: den Besucher durchlassen.