LaravelにCloudflare Turnstileを実装してみた3
前回までで、Turnstile を Laravel(カスタムパッケージ)に組み込むところまではできました。
とはいえ、実際に運用していくと「ローカルでどう確認する?」「CIで詰まらせないには?」「Featureテストではどこまで見る?」みたいな話が地味に効いてきます。
Turnstile は便利なんですが、Bot対策という性質上「外部サービスに依存している」ので、何も考えずにテストを書き始めると、たまに落ちる・環境で挙動が変わるみたいな事故が起きがちです。
そこで今回は、Cloudflare が用意している テストキーの使い分けと、
Featureテスト/CIでの扱い方を、実務メモとしてまとめます。
目次
1. ローカル環境・テスト環境でのTurnstile動作確認
Turnstile はありがたいことに、Cloudflare 公式が テストキーを用意してくれています。
これを使うと「本番キーを持っていない環境」でも、かなり本番に近い形で確認できます。
ここでの考え方としては、
- 普段の開発は「常に成功」キーで固定
- エラー確認のときだけ「常に失敗」キーに切り替え
- UX確認が必要なときだけ「チャレンジ強制」キーを使う
という運用が一番ラクでした。
1-1. 常に成功するテストキー(推奨)
TURNSTILE_SITE_KEY=1x00000000000000000000AA
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA挙動:
- ウィジェットは通常どおり表示される
- バリデーションは常に
success = true - ローカル環境やCIでも安定して通る
使い所:
- 通常のローカル開発
- CI/CDでの自動テスト
- フロントエンドの見た目確認
「基本はこれだけでいい」と思ってます。
Bot対策の動作確認が必要な場面でも、まずはこのキーが安定です。
フォーム送信時に cf-turnstile-response が送信されていれば成功します。
$rules['cf-turnstile-response'] = ['required', new Turnstile()];この状態で:
- ローカル環境
- ステージング
- Featureテスト(HTTP実通信なし)
すべて問題なく通ります。
1-2. 常に失敗するテストキー
TURNSTILE_SITE_KEY=2x00000000000000000000AB
TURNSTILE_SECRET_KEY=2x0000000000000000000000000000000AB挙動:
- ウィジェットは表示される
- バリデーションは常に
success = false - エラーハンドリングをテストできる
使い所:
- エラー表示のテスト
- バリデーション失敗時の挙動確認
- エラーメッセージのデザイン確認
普段は使いません。
「失敗したときにどうなるか」を確認したい時だけ、短時間で切り替える用途です。
また、明示的に Turnstile の検証失敗を確認したい場合は、
不正なレスポンストークンを送る方法でもいけます。
例:POSTデータに空文字を送る
$response = $this->post('/form/submit', [
'name' => 'テスト',
'email' => 'test@example.com',
'cf-turnstile-response' => '',
]);結果:
requiredに引っかかる- バリデーションエラーとしてフォームに戻る
ここは Turnstile の前に Laravel 側の required で止まるので、
「UIのエラー表示確認」には分かりやすいです。
例:ランダム文字列を送る
'cf-turnstile-response' => 'invalid-token',この場合、required は通りますが、
Turnstile Rule 内で success = false となり、
$fail('認証に失敗しました。もう一度お試しください。');が返ります。
1-3. チャレンジを強制するテストキー
TURNSTILE_SITE_KEY=3x00000000000000000000FF
TURNSTILE_SECRET_KEY=3x0000000000000000000000000000000FF挙動:
- ウィジェットが実際のチャレンジ(インタラクション)を表示
- 本番環境に近い動作を確認できる
- ユーザーが「確認」ボタンをクリックする必要がある
使い所:
- 本番に近いUX確認
- チャレンジ表示時のレイアウト確認
- ユーザー操作フローのテスト
Turnstile では、状況によって 追加チャレンジ(Challenge) が発生します。
ただし、これは以下の理由から ローカルでの再現はほぼ不要 と判断しました。
理由は以下です。
- チャレンジの有無は Cloudflare 側の判定
- フロントエンドで完結し、サーバー側は結果のみ受け取る
- サーバー側では
success = true / falseしか見ない
そのため、
チャレンジが出ても、最終的に成功すれば success = true
という前提で設計しています。
つまりこのキーは、「サーバー側のテスト」ではなく UX確認用ですね。
まとめ
| キー | 結果 | 主な用途 |
|---|---|---|
1x...AA | 常に成功 | 通常の開発・CI |
2x...AB | 常に失敗 | エラー処理のテスト |
3x...FF | チャレンジ表示 | 本番環境に近い動作確認 |
基本は 1x...AA(常に成功)でOKです。エラー処理を確認したいときだけ 2x...AB に切り替える、という使い方がおすすめです!
2. Featureテストでの扱い
結論から言うと、Featureテストでは
Turnstile 自体を検証対象にしないのがおすすめです。
理由は単純で、
- Turnstile の正しさは Cloudflare 側の責務
- 自分たちが担保したいのは「フォームが正しく動くこと」
- 外部通信が入るとテストが不安定になりがち
だからです。
2-1. 方法①:Turnstileを無効化する(おすすめ)
テスト環境(testing)では、
Turnstile のルールを追加しないようにします。
if (app()->environment('testing')) {
return $rules;
}運用的にはこれが一番ラクでした。
「テストが外部要因で落ちる」事故を避けられます。
2-2. 方法②:ダミートークンを送る
フォーム送信の形だけ揃えたい場合は、
テスト時にダミートークンを入れます。
$response = $this->post('/form/submit', [
// フォーム項目
'cf-turnstile-response' => 'test-token',
]);そのうえで、テスト環境では Turnstile Rule をスキップします。
(「フォーム送信の形」を本番と近づけたい時に便利です)
CIで詰まらないためのポイント
CI で詰まらせないために、個人的に意識したのはこの3つです。
- CI 環境では 必ずテストキーを設定
- 本番キーを
.env.testingに入れない - 外部通信に依存しない設計にする
.env.testing には、常に成功キーを入れておくのが安定です。
# .env.testing
TURNSTILE_SITE_KEY=1x00000000000000000000AA
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AAこれで GitHub Actions 等でも安定します。
まとめ(テストまわり)
- 通常は 常に成功するテストキーで十分
- 失敗系は
cf-turnstile-responseを壊すだけで確認可能 - チャレンジはサーバー側では気にしなくてよい(UX確認用)
- Featureテストでは Turnstile を責務外にするのが安全
Turnstile は
「テストで詰まらない Bot 対策」
という点でも、かなり扱いやすい印象でした。
まとめ
- カスタムパッケージ内でも問題なく導入できた
- Rule化+Bladeコンポーネントで責務分離しやすい
- reCAPTCHAよりUXがかなり軽い
「とりあえずBotを弾きたい」用途なら、Turnstile はかなり扱いやすい印象でした。
これで Turnstile シリーズはひとまず終了です。
今回は以上です!