Clerk の新規登録はブラウザーから Clerk へ直接送られ、Clerk にはユーザーを作成する前に動くフックがありません。そこで Bot Protection の判定は作成の直後に行います。新しいアカウントが最初に到達するページで 1 回だけ問い合わせ、block なら ban し、確認済みの印を付けます。それまでは、アプリ側でそのアカウントに何もさせません。これは Clerk 自身が紹介している オンボーディングのパターンを Next.js で実装したものです。
タグ
Next.js ガイドと同じく、ルートレイアウトの <ClerkProvider> の内側に置きます。decide 関数もそのガイドのものを使います。
呼び出し
Clerk の dashboard の Sessions で、ユーザーの public metadata をセッション token に追加します。これでサーバーは API を呼ばずに印を読み取れます。
{
"metadata": "{{user.public_metadata}}"
}
1 回だけ問い合わせてアカウントに印を付ける server action です。
// 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"; }
新しいアカウントが最初に開くページです。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 です。Clerk はこれを利便性のためのものと位置づけています。費用が発生したりメッセージを送ったりする route handler や server action では、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
訪問者の IP
判定は server action の中で行われるため、IP は Next.js ガイドの説明どおりです。新規登録時の Clerk 自身の bot 対策はオンのままでかまいません。Clerk のものはフォームを守り、Bot Protection はそのフォームで作られたアカウントを守ります。
アクションを適用する
読むのは action だけです。block の場合は、その操作がほかの理由で失敗したときと同じエラーを返し、bot に何も悟らせないようにします。challenge の場合は、すでにある確認手段(CAPTCHA、メールや電話の確認コード)を使います。何もなければそのまま続けます。アカウントは monitor mode で始まり、その間 action は常に allow です。block mode をオンにするまで、訪問者にとっては何も変わりません。
request_id を保存する
すべての応答には request_id が含まれます。操作で作成されたもの(アカウントや注文)のカラムに保存するか、その ID と一緒にログに残してください。お問い合わせの際にその判定を特定でき、後からどの応答が偽アカウントを通したのかを調べられます。
手元のマシンでテストする
Clerk の開発用キーで next dev を実行し、http://localhost:3000 を開きます。localhost 上のページは常にタグを受け付け、ドメインとして一覧に載ることはありません。ローカルでは IP が 127.0.0.1 または ::1 になりますが、問題ありません。自分のマシンの Docker でアプリを動かすと、代わりにブリッジのアドレス(172.17.0.1 など)になりますが、タグがそこであなたのページを見ているので、チャレンジページは localhost へ戻してくれます。
.env.localにKORZEN_API_KEYを設定し、新規登録ページを開きます。数秒後、ブラウザーの開発者ツールに_kzCookie が表示されます。- 新規登録します。判定が dashboard の判定ページに、送信したパスで表示されます。
- fail-open を確認します。誤ったキーを設定してもう一度新規登録すると、呼び出しは
401を返し、新規登録はそのまま完了します。
よくある間違い
- 印を middleware でしか確認しない。bot は API ルートを直接呼び出します。重要な箇所ではクレームを確認してください。
- 印を付けた後に
user.reload()を呼ばない。セッション token は最大 1 分間古い metadata を保持するため、middleware がアカウントを送り返してしまいます。 - 訪問のたびに問い合わせる。呼び出しはすべて判定としてカウントされます。印があれば 1 アカウントにつき 1 回で済みます。
- ブラウザーから
/v1/decideを呼び出す。API key がすべての訪問者に渡ってしまいます。呼び出しはサーバーで行ってください。 - タイムアウトがない、またはエラーで新規登録が止まる。
200以外の応答や、時間内に応答がない場合は、訪問者を通すという意味です。