はじめに — 認証なしフォームのジレンマ
Webサービスを開発していると、「認証なしで誰でも投稿できるフォーム」が必要になることがあります。
IdeaSpool では、外部から認証なしでアイデアやフィードバックを投稿できる「公開投稿ページ」を Claude Code で実装しました。サービスのユーザーでなくても、URLを知っていれば誰でもアイデアを送信できる機能です。
しかし、認証なしのフォームにはリスクがあります。ボットによるスパム投稿です。何も対策しなければ、大量のスパムが送り込まれ、サービスが使い物にならなくなる可能性があります。
この記事では、Cloudflare Turnstile を使ってボット対策を実装した経緯と、具体的な実装方法を解説します。
なぜ認証なしフォームが必要だったか
IdeaSpool はアイデア管理ツールです。自分のアイデアを整理するだけでなく、他の人からフィードバックを集める機能も提供しています。
2026年1月21日の開発日記には、こう書いています。
外部募集: 提供中サービスのユーザーから認証なしでフィードバックを募集したい
例えば、自分が運営しているWebサービスにフィードバックフォームを埋め込み、ユーザーから改善案を募集したいケースを想定していました。フィードバックを送る側に IdeaSpool へのログインを求めると、ハードルが上がって投稿数が減ってしまいます。
そこで、認証なしで投稿できる公開ページを作ることにしました。
ボット対策の選択肢
認証なしフォームのボット対策には、いくつかの選択肢があります。
| 方法 | メリット | デメリット |
|---|---|---|
| Google reCAPTCHA v2 | 広く使われている | 「私はロボットではありません」のチェックが面倒 |
| Google reCAPTCHA v3 | ユーザー操作不要 | スコア判定が曖昧なことがある |
| hCaptcha | プライバシー重視 | 画像選択が必要なことがある |
| Cloudflare Turnstile | 軽量、ユーザー操作不要、無料 | Cloudflare エコシステムに依存 |
| ハニーポット | シンプル | 高度なボットには効かない |
検討の結果、Cloudflare Turnstile を選びました。
開発日記には、選択理由がこう記録されています。
Turnstile: reCAPTCHA より軽量で UX が良い。Cloudflare なら無料
Turnstile を選んだ理由
-
ユーザー体験: チェックボックスをクリックしたり、画像を選択したりする操作が不要。バックグラウンドで自動的に検証が行われる
-
プライバシー: ユーザーの行動を追跡しない設計。GDPR 対応も容易
-
無料: Cloudflare アカウントがあれば、追加料金なしで利用可能
-
軽量: JavaScript のサイズが小さく、ページの読み込み速度への影響が少ない
Turnstile の仕組み
Turnstile は、目に見えないチャレンジ(課題)をブラウザに出して、人間とボットを区別します。
基本的な流れは以下の通りです。
- フォームページが読み込まれると、Turnstile のスクリプトが実行される
- ブラウザの環境情報やユーザーの行動パターンを分析
- 人間と判断されれば、トークンが発行される
- フォーム送信時に、そのトークンをサーバーに送信
- サーバーで Cloudflare API を呼び出し、トークンを検証
ユーザーから見ると、ほとんど何もせずにフォームを送信できます。必要に応じて、小さなウィジェットが表示されることがありますが、reCAPTCHA のような煩わしい操作は求められません。
実装のステップ
IdeaSpool での実装を例に、Turnstile の導入手順を解説します。
ステップ 1: Cloudflare でサイトを登録
Cloudflare のダッシュボードから「Turnstile」を選択し、サイトを登録します。登録すると、以下の2つのキーが発行されます。
- サイトキー: クライアント側で使用(公開可能)
- シークレットキー: サーバー側で使用(秘密にする)
ステップ 2: フロントエンドにウィジェットを配置
フォームページに Turnstile のスクリプトとウィジェットを追加します。
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form action="/api/submit" method="POST">
<input type="text" name="content" placeholder="アイデアを入力" />
<!-- Turnstile ウィジェット -->
<div class="cf-turnstile" data-sitekey="your-site-key"></div>
<button type="submit">送信</button>
</form>
React/Next.js を使っている場合は、@marsidev/react-turnstile のようなラッパーライブラリを使うと便利です。
import { Turnstile } from '@marsidev/react-turnstile';
function SubmitForm() {
const [token, setToken] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!token) {
alert('認証を完了してください');
return;
}
// token をサーバーに送信
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="content" />
<Turnstile
siteKey="your-site-key"
onSuccess={setToken}
/>
<button type="submit">送信</button>
</form>
);
}
ステップ 3: サーバーサイドでトークンを検証
フォームが送信されたら、サーバー側で Cloudflare API を呼び出してトークンを検証します。
開発日記には、重要な注意点が記録されています。
Cloudflare Turnstile はサーバーサイド検証が必須
クライアント側の検証だけでは不十分です。悪意のある攻撃者は、トークンを偽造したり、JavaScript を無効化したりできるからです。必ずサーバー側で検証を行う必要があります。
// Next.js API Route の例
export async function POST(request: Request) {
const formData = await request.formData();
const token = formData.get('cf-turnstile-response');
const content = formData.get('content');
// Turnstile トークンの検証
const verifyResponse = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
secret: process.env.TURNSTILE_SECRET_KEY!,
response: token as string,
}),
}
);
const verifyResult = await verifyResponse.json();
if (!verifyResult.success) {
return Response.json({ error: 'Bot detected' }, { status: 400 });
}
// 検証成功、投稿を処理
// ...
}
ステップ 4: ウィジェットの配置を調整
開発日記には、UX に関する試行錯誤も記録されています。
Turnstile の配置 送信ボタンの横か、フォームの下か → フォーム下に配置(視覚的に邪魔にならない)
Turnstile ウィジェットは、通常はコンパクトなチェックマークとして表示されます。送信ボタンの横に置くとボタンが押しにくくなることがあるため、フォームの下に配置するのが良いでしょう。
追加の防御レイヤー — レート制限
Turnstile だけでも多くのボットを防げますが、IdeaSpool では追加の防御として、Claude Code に依頼して IP ベースのレート制限も実装しました。
開発日記には、こう書いています。
IP ベースレート制限(10 件/時間)
これは、同じ IP アドレスから1時間に10件以上の投稿を制限するものです。Turnstile を突破した高度なボットや、人間による悪意のある大量投稿を防ぐための二重の防御です。
レート制限の実装方法
レート制限の実装にはいくつかの方法があります。
開発日記には、検討した選択肢が記録されています。
レート制限の実装方法 案 1: Redis(別サービス必要) 案 2: インメモリ(Vercel Serverless では共有不可) → v1 ではインメモリで暫定対応(後に Supabase テーブルに移行)
Vercel Serverless Functions はステートレスなので、インメモリでのレート制限は厳密には機能しません。しかし、初期段階では「ないよりマシ」程度の防御として採用し、後で Supabase のテーブルを使った永続的な実装に移行する計画としました。
本格的に実装する場合は、以下のような方法があります。
- Upstash Redis: Vercel と相性が良い、サーバーレス Redis
- Supabase テーブル: 既に Supabase を使っているなら追加コストなし
- Cloudflare Workers: Turnstile と同じ Cloudflare エコシステム
承認フローとの組み合わせ
IdeaSpool では、Turnstile + レート制限に加えて、承認フローも実装しました。
外部からの投稿は、すぐにアイデアとして登録されるのではなく、一旦「投稿」として保存されます。管理者が内容を確認し、承認した場合のみアイデアに変換されます。
開発日記には、AI 分析のタイミングについても記録があります。
承認時 AI 分析: スパム投稿にも AI を使うとコストがかかるので、承認時のみ実行
IdeaSpool では AI による自動タグ付けやスコアリング機能がありますが、これらの処理は承認後に実行します。スパム投稿にまで AI 処理を走らせると、API コストが無駄になるためです。
Turnstile 導入の効果
Turnstile を導入した結果、以下の効果がありました。
ボットの排除
公開投稿ページを公開してから、ボットによるスパム投稿は確認されていません。Turnstile がバックグラウンドで動作し、ボットを効果的にブロックしています。
ユーザー体験の維持
reCAPTCHA のような煩わしい操作がないため、ユーザーはスムーズにフィードバックを送信できます。「画像を選んでください」というチャレンジが表示されることもほとんどありません。
導入コストの低さ
Cloudflare アカウントがあれば無料で使えるため、追加の運用コストは発生していません。Claude Code を使った実装も比較的シンプルで、1日で導入できました。
注意点とベストプラクティス
Turnstile を導入する際の注意点をまとめます。
サーバーサイド検証は必須
繰り返しになりますが、クライアント側の検証だけでは不十分です。必ずサーバー側で Cloudflare API を呼び出してトークンを検証してください。
シークレットキーは秘密に
シークレットキーが漏洩すると、トークンを偽造される可能性があります。環境変数で管理し、Git にコミットしないようにしてください。
タイムアウトに注意
Turnstile のトークンには有効期限があります(通常300秒)。フォームの入力に時間がかかる場合は、送信時にトークンが期限切れになる可能性があります。
onExpire コールバックを使って、トークンが期限切れになったらウィジェットをリセットする処理を入れると良いでしょう。
<Turnstile
siteKey="your-site-key"
onSuccess={setToken}
onExpire={() => {
setToken(null);
// ウィジェットをリセット
}}
/>
多層防御を意識する
Turnstile だけに頼らず、複数の防御レイヤーを組み合わせることをお勧めします。
- Turnstile: ボットの基本的な排除
- レート制限: 大量投稿の防止
- 承認フロー: 人間による最終確認
- 禁止ワードフィルター: 明らかなスパムの自動排除
まとめ — ユーザー体験を損なわないボット対策
認証なしフォームのボット対策として、Cloudflare Turnstile は優れた選択肢です。
メリット:
- ユーザー操作不要、UX を損なわない
- 無料で利用可能
- 軽量で読み込み速度への影響が少ない
- 実装がシンプル
注意点:
- サーバーサイド検証は必須
- 多層防御と組み合わせるのが理想
IdeaSpool では、Turnstile + レート制限 + 承認フローの3層で防御しています。この組み合わせにより、スパムを効果的に排除しながら、正規ユーザーの体験を維持できています。
認証なしフォームを公開する際は、ぜひ Turnstile の導入を検討してみてください。reCAPTCHA に比べて導入のハードルが低く、ユーザー体験も良好です。
Zeronova(ゼロノバ)
Product Manager / AI-Native Builder
Web/IT業界19年以上・20以上のWebサービスを担当したPdM。東証プライム上場企業の子会社代表として事業経営を経験。現在はAIを駆使して企画から実装まで完結させる個人開発を実践中。