このモードは任意です。DNS フィルター、広告ブロッカー、または Korzen のホストをブロックするネットワークは、タグ、そのビーコン、チャレンジページを止めてしまいます。そうした訪問者もサーバーが送る情報だけで判定されて通過しますが、タグのトークンを必要とするルートでは先へ進めません。ファーストパーティモードは、この 3 つを自社ドメインのパス(別のパスを指定しない限り /kz)から配信し、訪問者のアドレスを添えて Bot Protection へ転送します。サイトそのものをブロックせずに、そのサイトのパスだけをブロックすることはできません。
パスを配信する方法は 2 つあり、両方を使うこともできます。
| 方法 | 有効にするもの | /kz を配信するもの |
|---|---|---|
| SDK のみ | Node では firstParty: true、Go では FirstParty: true | 自社のサーバー |
| Worker のみ | KORZEN_FIRST_PARTY = "1" と /kz/* のルート | 自社の Cloudflare Worker |
| Worker と SDK | 両方を同じパスで | Worker。チャレンジされた訪問者はサーバーが Worker へ送ります |
どの方法でも、ページは Korzen のホストではなく自社のパスからタグを読み込みます。タグは自身の src からアドレスを取るため、タグが送るリクエストもすべて /kz へ向かいます。
<script src="/kz/tag.js" data-site-key="YOUR_SITE_KEY" async></script>
このモードはすべての SDK にあります。Node(とその Worker)、Go、Python、PHP、Java、.NET で、オプションはそれぞれの README に記載しています。フレームワークガイドをもとに書いたクライアントにはフォワーダーがありません。その場合は SDK を使うか、Cloudflare の背後であれば、どの言語のオリジンにもこのモードを提供できる Worker だけを使ってください。
SDK のみ
自社のサーバーがパスを配信します。すでに Bot Protection に問い合わせているミドルウェアにオプションを渡し、同じオプションでパス専用のハンドラーを用意してください。クライアントは API キーを指定して createClient で作成したものでなければなりません。フォワーダーはリクエストごとにこのキーを Korzen へ送ります。
// Express, with korzen.js as the Express guide sets it up const options = { client: korzen, ip: (_get, req) => req.ip, firstParty: true }; app.use("/kz", protect(options)); // before any body parser: it answers /kz/... itself app.use(express.urlencoded({ extended: false })); app.post("/signup", protect(options), signup); // Next.js: app/kz/[...path]/route.js serves the path const kz = withKorzen(() => new Response(null, { status: 404 }), { client, ip: ipFrom.vercel, firstParty: true }); export { kz as GET, kz as POST };
o := korzen.Options{IP: korzen.RemoteIP, FirstParty: true} mux.Handle("/kz/", bot.Protect(http.NotFoundHandler(), o)) // the path: Protect answers it itself mux.Handle("POST /signup", bot.Protect(http.HandlerFunc(signup), o))
- 有効にするもの。
protectまたはwithKorzenにfirstParty: true(trueのときだけ有効)、Go ではOptions.FirstPartyです。そのうえで、ページのタグのsrcを/kz/tag.jsにします。訪問者のアドレスは判定と同じ方法で読み取ります。ipオプション、Go ではOptions.IPです。 - 訪問者が書き込めないアドレス。Korzen はフォワーダーがあなたのキーとともに送るアドレスを信頼するので、
ipオプションは訪問者が設定できないアドレスを読み取る必要があります。ipFrom.vercelは Vercel 上でのみ、ipFrom.cloudflare(Go ではkorzen.CloudflareIP)はサーバーが Cloudflare からのトラフィックしか受け付けない場合のみ、自前のロードバランサーや nginx の背後ではipFrom.lastForwardedを使います。訪問者が書き込めるヘッダーを使うと、Korzen が判定するアドレスをスクリプトが選べてしまいます。 - 訪問者にとって変わること。目に見える変化はありません。ブラウザはタグを読み込み、ビーコンを自社のドメインへ送ります。そのため、Korzen をブロックするネットワークの訪問者もタグの証拠をもとに判定され、トークンを必要とするアクションも行えます。パス配下のリクエストは判定されず、自社のハンドラーにも届きません。それ以外のパスはこれまでどおり判定されます。
- チャレンジページ。チャレンジされたページの読み込みは、Korzen のドメインではなく自社ドメインの
/kz/challenge?c=…&r=…へ送られ、サーバーがページ、そのファイル、送信を転送します。課題は同じで、訪問者は元のページへ戻ります。 - Fail-open。Bot Protection が遅い、停止している、またはサーバーから届かない場合、パス配下の各リクエストは空のボディで
503を返し、タグはそのシグナルを捨てます。ページやほかのルートは何も待ちません。Fail-open - 動作の確認。
curl -I https://shop.example/kz/tag.jsが200を返し、ブラウザのネットワークパネルでタグのリクエストが自社のドメインへ向かっていることを確認できます。動作の確認
Worker のみ
Cloudflare Worker がパスを配信します。サーバーは変更不要で、オリジンの言語も問いません。Cloudflare Workers のとおりに Worker を設定してから、モードを有効にし、パスのルートを追加してください。
# wrangler.toml
routes = [
{ pattern = "shop.example/kz/*", zone_name = "shop.example" },
{ pattern = "shop.example/signup*", zone_name = "shop.example" },
]
[vars]
KORZEN_URL = "https://korzen.dev"
KORZEN_FIRST_PARTY = "1"
# KORZEN_FIRST_PARTY_PATH = "/kz" # the path, /kz unless set
- 有効にするもの。
KORZEN_FIRST_PARTYを1またはtrueに設定し(0とfalse以外の値では無効のままで、Worker のログに警告が出ます)、shop.example/kz/*のルートを追加し、ページのタグのsrcを/kz/tag.jsにします。訪問者のアドレスは Cloudflare が設定するCF-Connecting-IPです。 - 訪問者にとって変わること。目に見える変化はありません。タグとビーコンは自社のドメインへ送られ、Cloudflare のエッジで応答されます。オリジンがそれらのリクエストを見ることはありません。Worker のルートはこれまでどおり判定されます。
- チャレンジページ。Worker がチャレンジしたページの読み込みは、自社ドメインの
/kz/challengeへ送られ、Worker がそのページを配信します。 - Fail-open。SDK と同じです。そのリクエストだけが空のボディで
503になり、ほかのリクエストはすべてオリジンへ進みます。Fail-open - 動作の確認。下記のとおりです。さらに
npx wrangler tailには/kz配下のリクエストが表示され、オリジンのアクセスログには 1 件も表示されません。動作の確認
Worker と SDK
サイトの前段に Worker があり、アプリケーションでも SDK を使っている場合(アプリケーションだけが知る context のため)は、両方で同じパスを指定してモードを有効にします。
- 有効にするもの。上記のとおり
/kz/*のルートを持つ Worker と、その背後の SDK のfirstParty: true(Go ではFirstParty: true)です。/kz/*には Worker 自身が応答するため、SDK がそれらのリクエストを見ることはありません。 - 訪問者にとって変わること。Worker のみの場合と同じです。タグ、ビーコン、チャレンジページは自社のドメインから、エッジで応答されます。
- チャレンジページ。SDK の
firstPartyは、SDK がチャレンジした訪問者を Korzen のホストではなく/kz/challengeへ送り、Worker がそのページを配信します。これがないと、SDK がチャレンジした訪問者は Korzen のホストへ送られ、ネットワークによってはブロックされます。 - Fail-open。パスを配信する側がそれぞれ単独で打ち切り、
503を返します。どちらも相手を待たせません。Worker のルートに/kz/*が含まれない場合や Worker を外した場合は、SDK が自らパスを配信します。その場合、Cloudflare の背後ではip: ipFrom.cloudflare(Go ではkorzen.CloudflareIP)を指定してください。 - 動作の確認。下記のとおりです。
npx wrangler tailには/kz配下のリクエストが表示され、サーバーのログには 1 件も表示されません。
パスが配信するもの
フォワーダー(SDK または Worker)は次のものだけに応答します。
| 自社のドメイン | 内容 |
|---|---|
GET /kz/tag.js | タグ。Korzen のキャッシュヘッダー付き |
GET /kz/n、POST /kz/s | タグの nonce とビーコン(ボディは最大 64 KiB) |
GET /kz/ping | フォワーダー自身が 204 で応答し、転送しません |
GET と POST /kz/challenge、GET /kz/challenge/wait | チャレンジページとその送信(最大 16 KiB) |
GET /kz/static/… | チャレンジページのスクリプト、スタイル、フォント |
/kz 配下のそれ以外のパスは 404、既知のパスでもメソッドが違えば 405 になり、どちらも Bot Protection へは届きません。そのため、このパスがオープンプロキシになることはありません。フォワーダーが Korzen へ送るのは、短いリストにあるブラウザ自身のヘッダー(User-Agent、Accept、Accept-Language、Content-Type、Origin、Sec-Fetch-*、クライアントヒント、If-None-Match)、bearer としての API キー、訪問者のアドレスです。Cookie、ブラウザの Authorization、X-Forwarded-* は決して送りません。戻ってくる応答は Korzen のキャッシュヘッダーとセキュリティヘッダーを保ち、Set-Cookie は決して含みません。
Korzen が訪問者のアドレスを受け入れるのは、これらのルートへのリクエストが自社の API キーを持つ場合だけで、ビーコンでは自社のサイトキーの場合だけです。それ以外は 403 で拒否されます。キーがブラウザに届くことはありません。
タグの _kz Cookie とストレージはこれまでどおり自社のドメインに残り、Korzen が受け取るデータも同じです。ブラウザが Korzen に直接接続したときだけ読める証拠の一部は、フォワーダー経由では得られません。タグのほかの証拠は届き、欠けているものが訪問者に不利に働くことはありません。
別のパス
firstPartyPath(Go では Options.FirstPartyPath、Worker では KORZEN_FIRST_PARTY_PATH)で別のパスを指定できます。たとえばブロックリストが /kz を覚えたときです。パスは / で始まり、/ で終わらず、A-Z a-z 0-9 . _ ~ - のセグメントで最大 64 文字です。読み取れないパスは一度だけログに記録され、モードは無効のままです。タグの src、SDK、Worker で同じパスを使ってください。
Fail-open
このパスがページやほかのルートを待たせることはありません。
- 転送されるリクエストはそれぞれ単独で打ち切られます。
GETは 1 秒、POSTは 2.5 秒です。タイムアウト、ネットワークエラー、4 MiB を超える応答、フォワーダーの障害は、空のボディとCache-Control: no-storeの503になります。リダイレクトはたどりません。Bot Protection 自身の4xxと5xxはそのまま返ります。 - 訪問者のアドレスがない場合(
ipオプションが何も読み取れない場合)、nonce、ビーコン、チャレンジは503を返します。タグとチャレンジページのファイルは引き続き配信されます。 - そのときタグは、Korzen のホストが停止しているときと同じように、そのシグナルを捨てます。タグが報告しなかったことがボットの証拠になることはありません。訪問者はサーバーが送る情報で判定されます。
動作の確認
curl -I https://shop.example/kz/tag.jsが200を、curl -I https://shop.example/kz/pingが204を返します。- タグのあるページでブラウザの開発者ツールを開くと、ネットワークパネルに自社ドメインの
/kz/tag.js、/kz/n、/kz/ping、/kz/sが200または204で表示され、Korzen のホストへのリクエストはありません。 - 保護されたアクションを一度通してから、セットアップページの「連携を確認する」を押してください。「その呼び出しに含まれるタグのトークン」が「検出済み」になります。判定はこれまでどおりダッシュボードの判定ページに表示されます。
Content-Security-Policyを使っている場合は、Korzen のホストを外せます。タグにはscript-src 'self'; connect-src 'self'; worker-src blob:で足ります。タグ
よくある間違い
- タグをまだ Korzen のホストから読み込んでいる。モードはパスを配信しますが、ブラウザをそこへ向かわせるのはタグの
srcだけです。 - Express でフォワーダーより前にボディパーサーがある。ビーコンのボディがフォワーダーに渡る前に読まれてしまいます。どのパーサーよりも前に、またはパスだけにマウントしてください。
KORZEN_FIRST_PARTYを設定した Worker に/kz/*のルートがない。リクエストはオリジンに届き、SDK がそこでパスを配信していなければ404になります。- パスが 2 つある。Worker と SDK で別のパスを使うと、SDK はチャレンジした訪問者を Worker が配信しないパスへ送ってしまいます。
/v1/decideを直接呼び出している。そのchallenge.urlは常に Korzen のホストです。訪問者を自社のパスへ送るのは SDK と Worker だけです。