Google reCAPTCHA の使い方(v2/v3)

reCAPTCHA は Google が提供する CAPTCHA(キャプチャ)認証システムで、現在 v2 と v3 が利用できます。以下はそれぞれのバージョンの基本的な使い方や設定方法(HTML の実装方法と PHP を使った検証方法)の覚書です。

reCAPTCHA は無料で利用できますが、1秒間に1000回(1ヶ月に100万回)以上のアクセスがある場合は reCAPTCHA Enterprise を使用するか、フォームに登録して許可を取得する必要があるようです。

reCAPTCHA 関連ページ

更新日:2024年02月21日

作成日:2020年3月17日

サンプルの表示で iframe を使っているので、スクロールして移動する際にちらつくことがあります。

reCAPTCHA にサイトを登録

reCAPTCHA を利用するには v2 または v3 のどちらのバージョンの場合も reCAPTCHA を使用するサイトを登録して API キー(reCAPTCHA の API を使用するためのキー)を取得する必要があります。

サイトの登録には Google のユーザーアカウントが必要です。

サイトの登録及び API キーの管理などは recaptcha/admin で行うことができます。

新しいサイトの登録は recaptcha/admin/create から行えます。

登録画面では、必要事項を入力します。

  • ラベル:キャプチャを識別するための名前です。自分でわかりやすい名前を付けます。
  • reCAPTCHA タイプ:利用するタイプを選択します。
  • ドメイン:reCAPTCHA を使用するドメインを指定します。
  • オーナー:Google アカウントのメールアドレスを指定します。

「reCAPTCHA 利用条件に同意する」にチェックを入れて「送信」をクリックして登録します。

入力に問題がなければ、API キー(サイトキーとシークレットキー)が表示されるのでコピーしておきます。API キーは recaptcha/admin で後から確認することができます。

  • Site Key:reCAPTCHA を表示する際に使用するサイトのキー
  • Secret Key:検証で使用する秘密のキーで公開してはいけません。

reCAPTCHA v2

v2 はウィジェット(キャプチャ)に表示される「私はロボットではありません」の横のチェックボックスにユーザがチェックを入れて認証する Checkbox タイプ(以下のような画像を表示)とチェックボックスを表示しない Invisible タイプ、Android用のタイプがあります。

以下で扱うのは reCAPTCHA v2 の Checkbox タイプです。

reCAPTCHA v2/Checkbox

セキュリティ設定

API キーの管理画面(recaptcha/admin)の「設定」でセキュリティの強度を3段階で設定することができます。

強度を上げると、チャレンジの画像が表示される可能性が高くなります。強度を下げるとユーザーの負担が減らせることができるので、運用に適した強度を設定します。

ウィジェットの表示(HTML 側の実装)

ブラウザに reCAPTCHA v2 のウィジェットを表示するには以下のような方法があります。

  • g-recaptcha タグ(g-recaptcha クラスを指定した要素)を使って自動的に表示
  • API のメソッド grecaptcha.render を使って(明示的に指定して)表示

違いは表示するウィジェットを既定のクラス名(g-recaptcha)を指定した要素で表示してその要素の data-xxxx 属性を使ってオプションを指定するか、API のメソッドを使って表示する要素やオプションを指定するかで、それにより API の読み込みのパラメータの指定が異なります。

※どちらの方法でも全く同じことができます。

g-recaptcha タグを使って表示

以下はフォーム要素内に g-recaptcha クラスを指定した div 要素(g-recaptcha タグ)を記述してウィジェットを表示する例です。

g-recaptcha タグ(13行目)には data-sitekey 属性を使ってサイトキーを指定する必要があります。

そして script タグを使って reCAPTCHA の API(api.js) を読み込みます(15行目)。

以下の例の場合は、g-recaptcha タグに data-callback 属性を指定してチェックボックスにチェックが入った時に呼び出されるコールバック関数(6行目〜8行目)を指定しています。

<html>
<head>
<title>Automatically render the reCAPTCHA widget</title>
<script>
//.g-recaptcha タグの data-callback 属性で指定したコールバック関数の定義
var myAlert = function(response) {
  alert("チェックボックスがチェックされました!");
};
</script>
</head>
<body>
  <form method="post" action="?">
    <div class="g-recaptcha" data-sitekey="サイトキー" data-callback="myAlert"></div>
  </form>
  <script src="https://www.google.com/recaptcha/api.js" async defer></script><!-- API の読み込み -->
</body>
</html>

reCAPTCHA の API(api.js)はデフォルトでは、最初に見つかった g-recaptcha クラスを指定した要素(g-recaptcha タグ)の data-sitekey 属性を調べて、サイトキーに問題がなければその要素にウィジェットを表示します。

g-recaptcha タグには、data-xxxx 属性を使ってウィジェットのサイズや色、コールバック関数を指定することができます(パラメータ参照)。

grecaptcha.render メソッドで表示

以下はウィジェットを表示する API のメソッド grecaptcha.render を(明示的に)指定してウィジェットを表示する例(表示する内容は前述の例と同じ)です。

API の読み込み(23行目)でパラメータ onload にページをロードした際に実行される関数の名前(この例の場合は onloadCallback)を、render に explicit を指定します。

API はパラメータ onload に指定した関数を実行する際(ページ読み込み時)に、ウィジェットを表示する grecaptcha.render メソッドを呼び出します。

grecaptcha.render メソッドには、ウィジェットを表示する要素の id 属性(この例では recaptcha)とパラメータの sitekey にサイトキー(必須)やコールバック関数を指定します(8行目〜11行目)。

※パラメータ onload に指定した関数は、API の読み込みより前に定義されている必要があります。

<html>
<head>
<title>Explicit render after an onload callback</title>
<script>
//onload callback function(ページをロードした際に実行される関数)
var onloadCallback = function() {
  //ウィジェットを表示するメソッド
  grecaptcha.render('recaptcha', {
    'sitekey' : "サイトキー",
    'callback' : myAlert //コールバック関数名
  });
};
//上記 'callback' で指定したコールバック関数の定義
var myAlert = function(response) {
  alert("チェックボックスがチェックされました!");
};
</script>
</head>
<body>
  <form method="post" action="?">
    <div id="recaptcha"></div>
  </form>
  <script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script><!-- API の読み込み -->
</body>
</html>

reCAPTCHA の API(api.js)のパラメータ render に explicit を指定すると onload に指定した関数(上記の例の場合は onloadCallback)を使ってウィジェットを表示することができます。

以下はウィジェットを表示するメソッド grecaptcha.render の書式です(パラメータ参照)。

grecaptcha.render()
grecaptcha.render('ウィジェットを表示する要素のid', {
  'sitekey' : "サイトキー",  //必須(sitekey 以外はオプション。)
  'theme' : "ウィジェットの色の指定",
  'size' : "ウィジェットのサイズ",
  'callback' : 成功した場合のコールバック関数名,
  'expired-callback' : 期限切れの場合のコールバック関数名,
  'error-callback' : エラーの場合のコールバック関数名
});
設定

API の読み込み

reCAPTCHA の API(api.js)の読み込みでは必要に応じてパラメータを指定することができます。

? に続けて「パラメータ=値」で指定します。複数ある場合は & でつなげます。

api.js?onload=関数名&render=表示方法&hl=言語
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit&hl=en" async defer></script> 

以下は API(api.js)の読み込みで指定できるパラメータです。

API(api.js)のパラメータ
パラメータ 説明
onload reCAPTCHA の依存ファイルの読み込みが完了した際に実行するコールバック関数の名前(オプション)。grecaptcha.render メソッドで表示する場合に指定
render explicit
または
onload
ウィジェットの表示方法。デフォルトは onload で最初に見つかった g-recaptcha にウィジェットを表示。grecaptcha.render メソッドで表示する場合は explicit を指定。(オプション)
hl 言語コード 特定の言語で表示する場合は言語コードを指定。指定しない場合(デフォルト)は、ユーザの環境を自動検出。(オプション)

ウィジェット表示の設定

ウィジェットの色やサイズ、ユーザがチェックを入れた際に呼び出される関数などを以下のような方法で設定することができます。

  • g-recaptcha タグを使って表示する場合:そのタグの data-xxxx 属性で指定
  • grecaptcha.render メソッドで表示する場合:メソッドのパラメータで指定
g-recaptcha タグの例
<div class="g-recaptcha" data-sitekey="サイトキー" data-callback="myAlert" data-theme="dark"></div>
grecaptcha.render メソッドの例
grecaptcha.render('recaptcha', {
  'sitekey' : "サイトキー",
  'callback' : myAlert,
  'theme' : 'dark'
});

以下は g-recaptcha タグの属性と grecaptcha.render メソッドのパラメータに指定できる値です。サイトキーのみが必須で、その他は全てオプションです。

ウィジェット表示の設定項目
g-recaptcha
属性
grecaptcha.render
パラメータ
説明
data-sitekey sitekey 取得したサイトキー(必須)
data-theme theme ウィジェットの色(dark または light)を指定。デフォルトは light。
data-size size ウィジェットのサイズ(compact または normal)を指定。デフォルトは normal。
data-tabindex tabindex ウィジェットやチャレンジ画像の tabindex の値を指定。デフォルトは 0。
data-callback callback ユーザがウィジェットにチェックを入れて成功した場合に呼び出されるコールバック関数の名前。コールバック関数はパラメータ response に g-recaptcha-response の値を受け取ります。
data-expired-callback expired-callback ユーザーがウィジェットにチェックを入れた後に一定の期間(2分間)が過ぎてユーザが再度チェックする必要がある場合に呼び出されるコールバック関数の名前。
data-error-callback error-callback エラー(通常はネットワーク接続のエラー)が発生した場合に呼び出されるコールバック関数の名前。
g-recaptcha-response(レスポンス)

ユーザがウィジェットにチェックを入れて成功すると、API により以下のようなレスポンス(トークン)が生成されます。

03AERD8Xr85M-qcg5EoivhRUI9RgNagFmqwGUKZcXupYI_2tcBLncMtupBlxGiT4C0YYOUI7R4k3o
NX4OCYI9kq83tmTqG1MuNEIexHli-qNd5FLE7XKpOi3Tliy5ZVUFmaeg1qwCnzGUKn1Pd7ae79xn
・・・中略・・・
PEn1Pd7ae9fX_aeg1fNQME9mNNCnzMp8_ATvQ

この値を使ってサーバ側で検証を行います。この値は以下の方法で取得することができます。

  • コールバック関数のパラメータ: function(response) の response
  • grecaptcha.getResponse メソッド(返り値)
  • フォームを送信した際に生成される $_POST["g-recaptcha-response"]

このレスポンス(g-recaptcha-response)はトークンとも呼ばれ、有効期間は2分間です。

JavaScript API

以下の reCAPTCHA API のメソッドが使用できます。

メソッド(書式) 説明
grecaptcha.render(
  container,
  parameters
)
container に指定された要素にウィジェットを表示し、生成したウィジェットの ID を返します。
container :
ウィジェットを表示する要素の id 属性または DOM 要素。
parameters:
key=value のペアから成るオブジェクト。
例:{"sitekey": "your_site_key", "theme": "light"}
指定できるパラメータはgrecaptcha.renderパラメータを参照。
grecaptcha.reset(
opt_widget_id
)
ウィジェットをリセットします。
opt_widget_id:ウィジェットの ID(オプション)。指定しない場合は、最初に生成されたウィジェット。
grecaptcha.getResponse(
opt_widget_id
)
ウィジェットの response を取得します。
opt_widget_id:ウィジェットの ID(オプション)。指定しない場合は、最初に生成されたウィジェット。
HTML 側の実装例

以下はウィジェットと送信ボタンを表示し、「わたしはロボットではありません」のチェックボックスにチェックを入れて成功すると送信ボタンが有効になり送信できるようにする例です。

また、チェックを入れてから2分が経過すると期限切れになるので、その場合は「送信するにはチェックを入れてください」を再表示して送信ボタンを無効にしています。

以下が上記サンプルの実装例です。g-recaptcha タグを使ってウィジェットを表示しています。

22行目の g-recaptcha タグ(.g-recaptcha を指定した div 要素)に以下の属性を設定しています。

  • data-sitekey 属性:取得したサイトキー
  • data-callback 属性:チェックを入れて成功した場合に呼び出されるコールバック関数
  • data-expired-callback 属性:チェックを入れた後に2分経過した場合に呼び出されるコールバック関数

script タグ(4〜17行目)で上記属性で指定したコールバック関数を定義します。これらの定義は API の読み込み(28行目)より前に記述します。

5〜10行目:ユーザーがウィジェットにチェックをいれて成功した場合に呼び出されるコールバック関数(verifyCallback)の定義です。「送信するにはチェックを入れてください」を削除して(空にして)、送信ボタンの disabled 属性を削除して送信できるようにしています。

11〜16行目:チェックを入れた後に2分経過した場合に呼び出されるコールバック関数(expiredCallback)の定義です。「送信するにはチェックを入れてください」を表示して、送信ボタンに disabled 属性を設定して無効にしています。

<html lang="ja">
<head>
<title>g-recaptcha タグを使って表示</title>
<script>
  var verifyCallback = function(response) { //コールバック関数の定義
    //#warning の p 要素のテキストを空にf
    document.getElementById("warning").textContent = '';
    //#send の button 要素の disabled 属性を解除
    document.getElementById("send").disabled = false;
  };
  var expiredCallback = function() { //コールバック関数の定義
    //#warning の p 要素のテキストに文字列を設定
    document.getElementById("warning").textContent = '送信するにはチェックを・・・';
    //#send の button 要素に disabled 属性を設定
    document.getElementById("send").disabled = true;
  };
</script>
</head>
<body>
  <h1>g-recaptcha タグを使って表示</h1>
  <form method="post" action="?">
    <div class="g-recaptcha" data-sitekey="サイトキー" data-callback="verifyCallback" data-expired-callback="expiredCallback"></div>
    <p id="warning">送信するにはチェックを入れてください。</p>
    <button id="send" type="submit" disabled>送信</button>
  </form>
  <script src="https://www.google.com/recaptcha/api.js" async defer></script>
</body>
</html>

上記のような方法で、ウィジェットにチェックを入れたら送信できるようになりますが、これだけではユーザのレスポンスは検証されていないのでサーバー側での検証が必要です。

以下はウィジェットと送信ボタンを表示し、「わたしはロボットではありません」のチェックボックスにチェックを入れずに送信ボタンをクリックすると「チェックを入れてください」と表示して送信を中断する例です。

以下の例では、grecaptcha.render メソッドを使ってウィジェットを表示し、送信ボタンがクリックされた場合の処理は jQuery を使っています。

以下が上記サンプル実装例です。

ウィジェットを表示する要素(8行目)には id 属性(この例では recaptcha)を付与しておきます。

API の読み込み(37行目)ではパラメータ(?onload=onloadCallback&render=explicit)を指定します。

script タグ(11〜36行目)で、ウィジェットを表示するメソッドを使ってサイトキーやコールバック関数を指定し、それらのコールバック関数を定義します。

40〜59行目:jQuery を使って、ウィジェットにチェックが入ってない場合(ウィジェットの要素に verified クラスが付与されていない場合)はエラーメッセージを表示し送信を中止する処理。

<html lang="ja">
<head>
<title>grecaptcha.render メソッドで表示</title>
</head>
<body>
  <h1>grecaptcha.render メソッドで表示</h1>
  <form id="rc_form" method="post" action="?">
    <div id="recaptcha"></div>
    <button id="send" type="submit">送信</button>
  </form>
<script>
  var onloadCallback = function() {
    //ウィジェットを表示するメソッド
    grecaptcha.render('recaptcha', {
      'sitekey' : "サイトキー",
      'callback' : verifyCallback,
      'expired-callback' : expiredCallback
    });
  };
  //チェックを入れて成功した場合に呼び出されるコールバック関数
  var verifyCallback = function(response) {
    //ウィジェットの要素に verified クラスを設定
    document.getElementById('recaptcha').className = "verified";
    //エラーメッセージを表示する要素
    var recaptchaError = document.getElementById('recaptcha_error');
    if(recaptchaError != null) {
       //エラーメッセージを表示する要素が存在すれば削除
       recaptchaError.parentNode.removeChild(recaptchaError);
    }
  };
  //期限切れの場合に呼び出されるコールバック関数
  var expiredCallback = function() {
    //verified クラスを削除
    document.getElementById('recaptcha').classList.remove('verified');
  };
</script>
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
jQuery(function($){
  //送信ボタンがクリックされた場合の処理
  $("#rc_form").submit(function(){
    //エラー表示の初期化
    $("p.error").remove();
    //reCAPTCHA にチェックが入っているかを確認
    //ウィジェットの要素に verified クラスが付与されていない場合
    if(!$('#recaptcha').hasClass('verified')) {
      //エラーメッセージの要素を追加
      $('#recaptcha').append("<p class='error' id='recaptcha_error'>チェックを入れてください</p>");
    }
    //実際のフォームではその他の input 要素などの検証処理も一緒に記述

    //.error があれば送信しない
    if($(".error").length > 0){
      //alert("Error");
      return false;
    }
  });
})
</script>
</body>
</html>

上記の例の場合も、ユーザのレスポンスは検証されていないのでサーバー側での検証が必要です。

PHP を使った検証(v2)

reCAPTCHA を設置しただけでは意味がないので、サーバ側でレスポンスがユーザにより生成されたものかを検証する必要があります。以下はおおまかな流れです。

  1. ユーザのレスポンス(トークン)を取得($_POST[ 'g-recaptcha-response' ])
  2. API を使ってトークンを検証(API にトークンを送信して検証結果を取得)
  3. API から取得した値(検証結果)を基に判定

ユーザーが reCAPTCHA のウィジェットにチェックを入れて成功すると、トークン(g-recaptcha-response)が生成されます。

この値(トークン)は2分間のみ有効で、検証は1度だけ実行することができます。繰り返し同じ値を使って検証することはできません。

トークンの値は以下のいずれかの方法で取得することができます(v2 の場合)。

  • ユーザがフォームを送信した際に含まれる $_POST["g-recaptcha-response"] の値
  • ユーザがウィジェットにチェックを入れて成功した後で、API の grecaptcha.getResponse() メソッドを使って取得
  • grecaptcha.render メソッドのパラメータ callback や g-recaptcha タグの data-callback 属性で指定したコールバック関数に渡されるパラメータ response の値

公式サイト:Verifying the user's response

トークンの検証

トークン検証するには API にリクエストを送信(METHOD: POST)してその返り値を解析します。

API の URL: https://www.google.com/recaptcha/api/siteverify

以下のパラメータを指定します。

API Request(パラメータ)
パラメータ 説明
secret (必須)取得したシークレットキー
response (必須)トークンの値。例:$_POST["g-recaptcha-response"]
remoteip (オプション)ユーザーの IP アドレス

以下はシークレットキーを変数 $secretKey に格納した場合の API Request の URL の例(? に続けて「パラメータ=値」で指定する場合)です。

'https://www.google.com/recaptcha/api/siteverify?secret=' . $secretKey . '&response=' . $_POST[ 'g-recaptcha-response' ]

返り値(API Response)は file_get_contents() cURL 関数を使って取得することができます(または reCAPTCHA のライブラリをインストールして使うこともできます)。

以下は file_get_contents() を使って API Response(レスポンス)を取得する例です。

//API Request URL
$url = 'https://www.google.com/recaptcha/api/siteverify';
//パラメータを指定
$data = array(
  //シークレットキー
  'secret' => $secretKey,
  //POST されたトークンの値
  'response' =>  $_POST[ 'g-recaptcha-response' ]
);
//POST メソッドを使用
$context = array(
  'http' => array(
    // POST メソッドを指定
    'method'  => 'POST',
    'header'  => implode("\r\n", array('Content-Type: application/x-www-form-urlencoded',)),
    'content' => http_build_query($data)
  )
);
//上記パラメータを指定して file_get_contents で API Response を取得
$api_response = file_get_contents($url, false, stream_context_create($context));

API の URL にパラメータを指定して、以下のように(POST メソッドなどを指定せずに)レスポンスを取得することもできますが、正しい方法かはわかりません。

$api_url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . $secretKey . '&response=' . $_POST[ 'g-recaptcha-response' ];
$api_response = file_get_contents( $api_url );

返り値(レスポンス)の判定

API からの返り値は以下のような JSON データ形式になっていて、success プロパティが true ならトークンは有効です。

有効と判定された場合の例
{
  "success": true,  //有効
  "challenge_ts": "2020-03-19T04:27:46Z",
  "hostname": "webdesignleaves.localhost"
}

以下は success プロパティが false で無効と判定された例です。error-codes プロパティにエラーの原因が入っていて、この場合は "timeout-or-duplicate" とあり、期限切れまたは同じ値を再度検証しようとしたためとわかります。

無効と判定された場合の例
{
  "success": false,  //無効
  "error-codes": [ "timeout-or-duplicate" ]
}

JSON データ形式の返り値は json_decode() を使ってプロパティの値を取得(JSON 文字列をデコード)することができます。

<?php
$json = '{
   "success" : true,
   "challenge_ts" :"2020-03-19T04:27:46Z",
   "hostname" : "webdesignleaves.localhost"
 }';

//JSON 文字列をデコード
$obj = json_decode($json);

if($obj->success){
  //この場合 success と出力される
  echo "success";
}
echo $obj->hostname; //webdesignleaves.localhost
?>

エラーコード(配列)
コード 説明
missing-input-secret シークレットキーがありません
invalid-input-secret シークレットキーが有効ではありません
missing-input-response トークン(response)がありません
invalid-input-response トークン(response)が有効ではありません
bad-request 無効なリクエスト
timeout-or-duplicate トークン(response)が期限切れか、または既に使われた値です
PHP の実装例(v2)

以下は、送信ボタンをクリックすると PHP で検証するサンプルです。ウィジェットにチェックを入れない場合にも、送信ボタンをクリックできますが、その場合は検証に失敗するのでエラーコードを表示します。

ウィジェットにチェックを入れて検証が通れば「成功」と表示します。

PHP 側の実装ではシークレットキーを使用するので、シークレットキーは別ファイル(以下の例では recaptcha_vars.php)に保存して require などを使って読み込むようにしています。

シークレットキーを記述したファイルはパブリックからアクセスできない場所に配置するか、.htacess などを使って外部からアクセスできないようにします。

recaptcha_vars.php
<?php
// reCAPTCHA v2 サイトキー
define('V2_SITEKEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx');
// reCAPTCHA v2 シークレットキー
define('V2_SECRETKEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx');

サイトキーは HTML のソースを見ればわかってしまいますが、サイトキーもまとめておくことで変更があった場合など管理が楽です。

以下が上記サンプルのコードです。

この例では POST メソッドでトークンを自分自身(現在のファイル)に送信して検証しています。

form 要素の action 属性はこのファイル自信に送信するので省略しています(HTML5 の場合)。

また、以下の HTML の実装例では g-recaptcha タグを使ってウィジェットを表示し、ウィジェットにチェックが入っているかどうかは確認していません。

<?php
// サイトキーとシークレットキーを記述したファイルの読み込み
require 'libs/recaptcha_vars.php';
// reCAPTCHA サイトキー
$siteKey = V2_SITEKEY;
// reCAPTCHA シークレットキー
$secretKey = V2_SECRETKEY;

$result_status = '';  // 結果を表示する文字列を初期化
// トークンが送信されたら
if ( isset( $_POST[ 'g-recaptcha-response' ] ) ) {

  //API Request URL(リクエストを送る API の URL)
  $url = 'https://www.google.com/recaptcha/api/siteverify';
  //パラメータを指定
  $data = array(
    'secret' => $secretKey, //シークレットキー
    'response' =>  $_POST[ 'g-recaptcha-response' ]
  );
  //POST メソッドを使用
  $context = array(
    'http' => array(
      'method'  => 'POST',
      'header'  => implode("\r\n", array('Content-Type: application/x-www-form-urlencoded',)),
      'content' => http_build_query($data)
    )
  );
  //上記パラメータを指定して file_get_contents で API Response を取得
  $api_response = file_get_contents($url, false, stream_context_create($context));

  // JSON をデコード
  $result = json_decode( $api_response );
  // トークンが有効な場合
  if ( $result->success ) {
    $result_status = '成功';
    // 成功した場合の処理(メールの送信など)を実行(または結果を変数に入れて、その変数を使って処理を分岐するなど)
  } else { // トークンが無効な場合
    $result_status = '失敗: ';
    // error-codes は配列(以下は最初のエラーを取得)
    $result_status .= $result->{'error-codes'}[ 0 ];
  }
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<title>reCAPTCHA v2 Sample</title>
</head>
<body>
  <h1>PHP を使った検証(1)</h1>
  <form id="rc_form" method="post">
    <div class="g-recaptcha" data-sitekey="<?php echo $siteKey; ?>"></div>
    <button type="submit">送信</button>
  </form>
  <p><?php echo $result_status; ?></p>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</body>
</html>

以下は PHP の実装部分は同じですが、HTML の実装ではウィジェットにチェックが入っているかどうかを確認しています。

また、ウィジェットの表示は grecaptcha.render メソッドを使っています。

<?php
require 'libs/recaptcha_vars.php';
// reCAPTCHA サイトキー
$siteKey = V2_SITEKEY;
// reCAPTCHA シークレットキー
$secretKey = V2_SECRETKEY;

$result_status = '';  // 結果を表示する文字列を初期化
if ( isset( $_POST[ 'g-recaptcha-response' ] ) ) {
  //API Request URL(リクエストを送る API の URL)
  $url = 'https://www.google.com/recaptcha/api/siteverify';
  //パラメータを指定
  $data = array(
    'secret' => $secretKey, //シークレットキー
    'response' =>  $_POST[ 'g-recaptcha-response' ]
  );
  //POST メソッドを使用
  $context = array(
    'http' => array(
      'method'  => 'POST',
      'header'  => implode("\r\n", array('Content-Type: application/x-www-form-urlencoded',)),
      'content' => http_build_query($data)
    )
  );
  //上記パラメータを指定して file_get_contents で API Response を取得
  $api_response = file_get_contents($url, false, stream_context_create($context));
  $result = json_decode( $api_response );
  if ( $result->success ) {
    $result_status = '成功';
    // 成功した場合の処理(メールの送信など)を実行(または結果を変数に入れて、その変数を使って処理を分岐するなど)
  } else {
    $result_status = '失敗: ';
    // error-codes は配列(以下は最初のエラーを取得)
    $result_status .= $result->{'error-codes'}[ 0 ];
  }
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<title>reCAPTCHA v2 Sample</title>
<script>
  var onloadCallback = function() {
    grecaptcha.render('recaptcha', {
      'sitekey' : "<?php echo $siteKey; ?>",
      'callback' : verifyCallback,
      'expired-callback' : expiredCallback
    });
  };
  var verifyCallback = function(response) {
    document.getElementById("warning").textContent = '';
    document.getElementById("send").disabled = false;
  };
  var expiredCallback = function() {
    document.getElementById("warning").textContent = '送信するにはチェックを入れてください。';
    document.getElementById("send").disabled = true;
  };
</script>
</head>
<body>
  <h1>PHP を使った検証(2)</h1>
  <form id="rc_form" method="post">
    <div id="recaptcha"></div>
    <p id="warning">送信するにはチェックを入れてください。</p>
    <button id="send" type="submit" class="btn btn-primary" disabled><span>送信</span></button>
  </form>
  <p><?php echo $result_status; ?></p>
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
</body>
</html>

cURL 関数 を使う

file_get_contents() を利用するには php.ini の設定で allow_url_fopen=on になっている必要があります。

おそらくデフォルトでは有効になっている思いますが、セキュリティ上の理由で off にしている場合や on に変更できない場合は file_get_contents() の代わりに cURL 関数を使って API Response を取得することができます。

以下は前述の例と全く同じことを cURL 関数を使って行う場合の例です。HTML の実装部分は全く同じなので PHP 部分のみを記載します。

<?php
require 'libs/recaptcha_vars.php';
// reCAPTCHA サイトキー
$siteKey = V2_SITEKEY;
// reCAPTCHA シークレットキー
$secretKey = V2_SECRETKEY;

$result_status = '';  // 結果を表示する文字列を初期化
if ( isset( $_POST[ 'g-recaptcha-response' ] ) ) {
  //cURL セッションを初期化
  $ch = curl_init();
  // curl_setopt() により転送時のオプションを設定
  //API の URL の指定
  curl_setopt($ch, CURLOPT_URL,"https://www.google.com/recaptcha/api/siteverify");
  //HTTP POST メソッドを使う
  curl_setopt($ch, CURLOPT_POST, true );
  //API パラメータの指定
  curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
    'secret' => $secretKey,
    'response' => $_POST[ 'g-recaptcha-response' ]
  )));
  //curl_execの返り値を文字列にする
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  //転送を実行してレスポンスを $json に格納
  $json = curl_exec($ch);
  //セッションを終了
  curl_close($ch);

  //レスポンスの $json(JSON形式)をデコード
  $result = json_decode( $json );

  if ( $result->success ) {
    $result_status = '成功';
    // 成功した場合の処理(メールの送信など)を実行(または結果を変数に入れて、その変数を使って処理を分岐するなど)
  } else {
    $result_status = '失敗: ';
    // error-codes は配列(以下は最初のエラーを取得)
    $result_status .= $result->{'error-codes'}[ 0 ];
  }
}
?>

以下は上記実装例のサンプルです。

以下は reCAPTCHA v2 を使ったコンタクトフォームのサンプルです。以下のサンプルでは実際にはメールは送信されません。

サンプルを別ページで開く

コードの全文を別ページで開く

関連ページ:お問い合わせ(メール)フォームの設置/reCAPTCHA v2 を使う

ライブラリを使う(v2)

API に直接リクエストを送信するのではなく、reCAPTCHA のライブラリをインストールして検証することもできます(ライブラリが API にリクエストを送信)。

v2 と v3 に対応しています。

Composer を使ってのインストールが推奨されていますが、reCAPTCHA PHP client library から直接ダウンロードして使うこともできます。

関連ページ:Composer のインストールと使い方

以下は Mac で Composer を使って reCAPTCHA のライブラリをインストールする例です。

ターミナルでライブラリをインストールするディレクトリに移動して、composer require コマンドで簡単にインストールすることができます。

$ cd /Applications/MAMP/htdocs/xxxx/reCAPTCHA  return #ディレクトリに移動

$ composer require google/recaptcha "^1.2"  return # ライブラリをインストール
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing google/recaptcha (1.2.3): Downloading (100%)
Writing lock file
Generating autoload files

$ tree  return # 内容を確認
.
├── composer.json
├── composer.lock
└── vendor
    ├── autoload.php
    ├── composer
    │   ├── ClassLoader.php
     ・・・中略・・・
    └── google
        └── recaptcha
            ├── ARCHITECTURE.md
            ├── CONTRIBUTING.md
            ├── LICENSE
            ・・・中略・・・
            ├── src
            │   ├── ReCaptcha
            │   │   ├── ReCaptcha.php
            │   │   ├── RequestMethod
            │   │   │   ├── Curl.php
            │   │   │   ├── CurlPost.php
            │   │   │   ├── Post.php
            │   │   │   ├── Socket.php
            │   │   │   └── SocketPost.php
            │   │   ├── RequestMethod.php
            │   │   ├── RequestParameters.php
            │   │   └── Response.php
            │   └── autoload.php
            └── tests
                └── ReCaptcha
                    ├── ReCaptchaTest.php
                    ・・・中略・・・
                    └── ResponseTest.php

11 directories, 46 files
    

インストールすると、以下のような composer.json ファイルが生成されます(参考まで)。

composer.json
{
    "require": {
        "google/recaptcha": "^1.2"
    }
}

以下はインストールしたライブラリを使って検証する場合の例です。HTML の実装部分は、前述の例と全く同じなので PHP 部分のみを記載します。

9行目ではインストールしたライブラリを読み込んでいます。この例では Composer を使ってインストールしたので Composer の autoload.php を指定しています。直接ダウンロードした場合は、reCAPTCHA の autoload.php を指定する必要があります。

トークンを検証するには、シークレットキーを渡して reCAPTCHA のインスタンスを生成し、verify() メソッドで検証します。

以下の例では setExpectedHostname($_SERVER['SERVER_NAME']) で hostname がマッチするかも確認しています(オプション)。

<?php
require 'libs/recaptcha_vars.php';
// reCAPTCHA サイトキー
$siteKey = V2_SITEKEY;
// reCAPTCHA シークレットキー
$secretKey = V2_SECRETKEY;

// reCAPTCHA ライブラリの読み込み
require 'reCAPTCHA/vendor/autoload.php';

$result_status = '';  // 結果を表示する文字列を初期化

if ( isset( $_POST[ 'g-recaptcha-response' ] ) ) {
  //インスタンスを生成
  $recaptcha = new \ReCaptcha\ReCaptcha( $secretKey );
  // ホスト名を確認し verify() メソッドで検証して結果を $resp に格納
  $resp = $recaptcha->setExpectedHostname($_SERVER['SERVER_NAME'])
    ->verify( $_POST[ 'g-recaptcha-response' ], $_SERVER[ 'REMOTE_ADDR' ] );
  //結果により処理を分岐
  if ( $resp->isSuccess() ) {  //認証に成功した場合
    $result_status = '成功';
    // 成功した場合の処理(メールの送信など)を実行(または結果を変数に入れて、その変数を使って処理を分岐するなど)
  } else {  //認証に失敗した場合
    $errors = $resp->getErrorCodes();
    // $errors は配列(以下は最初のエラーを取得)
    $result_status = '失敗: ' . $errors[ 0 ];
  }
}
?>

利用可能なメソッド

利用可能なメソッド
メソッド バージョン 説明
setExpectedHostname($hostname) v2/v3 ホスト名がマッチするかを確認する
setExpectedAction($action) v3 action がマッチするかを確認する
setScoreThreshold($threshold) v3 スコアのしきい値を設定する
setChallengeTimeout($timeoutSeconds) v2/v3 タイムアウトの時間を設定する

上記の set*() メソッドは ReCaptcha のインスタンスを返すので以下のようにつなげて実行(chain)することができます。

v3 の場合の例
<?php
$recaptcha = new \ReCaptcha\ReCaptcha($secret);
$resp = $recaptcha->setExpectedHostname('example.com')
                  ->setExpectedAction('homepage')
                  ->setScoreThreshold(0.5)
                  ->verify($gRecaptchaResponse, $remoteIp);

if ($resp->isSuccess()) {
  // Verified! 認証成功
} else {
  $errors = $resp->getErrorCodes();
}

verify() メソッドの返り値

verify() メソッドは検証結果のオブジェクトを返します。以下のようにインスタンスのメソッドを使って検証結果の値を確認することができます。

//検証結果を $resp に代入
$resp = $recaptcha->verify( $_POST[ 'g-recaptcha-response' ], $_SERVER[ 'REMOTE_ADDR' ] );

echo $resp->isSuccess();  //成功していれば 1 (true)
echo $resp->getHostname();  //ホスト名
echo $resp->getChallengeTs(); //実行時刻
print_r( $resp->getErrorCodes()); //エラー(配列)

以下は verify() メソッドの返り値の例です。

成功した場合の例
ReCaptcha\Response Object
(
  [success:ReCaptcha\Response:private] => 1
  [errorCodes:ReCaptcha\Response:private] => Array
    (
    )

  [hostname:ReCaptcha\Response:private] => webdesignleaves.localhost
  [challengeTs:ReCaptcha\Response:private] => 2020-03-19T08:19:06Z
  [apkPackageName:ReCaptcha\Response:private] =>
  [score:ReCaptcha\Response:private] =>
  [action:ReCaptcha\Response:private] =>
)
失敗した場合の例
ReCaptcha\Response Object
(
  [success:ReCaptcha\Response:private] =>
  [errorCodes:ReCaptcha\Response:private] => Array
    (
        [0] => timeout-or-duplicate
    )

  [hostname:ReCaptcha\Response:private] =>
  [challengeTs:ReCaptcha\Response:private] =>
  [apkPackageName:ReCaptcha\Response:private] =>
  [score:ReCaptcha\Response:private] =>
  [action:ReCaptcha\Response:private] =>
)

reCAPTCHA v3

reCAPTCHA v3 はユーザーがチェックを入れたり画像を選択する必要のないシステムで、ユーザーと Web ページとのインタラクション(やり取り)に基づいて各リクエストのスコアを返します。

検証の際はそのスコアをもとに処理を分岐させることができます。

実装するとページの右下に以下のような画像が表示されます。

画像にマウスオーバーすると以下のような表示になります。

reCAPTCHA にサイトを登録する際に、reCAPTCHA タイプで v3 を選択してサイトキーとサイトシークレットを取得します。

スコア

reCAPTCHA v3 はリクエストが人間によるものかボットによるものかの判定をスコア(0.0~1.0の数値)で返します(Interpreting the score)。

検証ではこのスコアをもとに処理を分岐します。

アクション

アクションは reCAPTCHA v3 で導入された新しいコンセプトです(Actions)。

reCAPTCHA v3 の管理画面(recaptcha/admin)でアクションごとの情報を確認することができるようです。アクション毎の TOP10 の分布などが表示されます(以下の例では、実績がないためかまだ表示されていません)。

検証の際にはスコアと共にアクション名も検証するべきとされています。

どのようなアクションのページにどのようなスコアが判定されるかなどを解析して適切な設定ができるようになっているようですが未検証です。

また、注意書きとして「アクションには英数字とスラッシュのみを含めることができ、ユーザー固有のものであってはなりません」とあるので任意の名前を付けられそうですが、「ユーザー固有のものであってはなりません」の意味がよくわかりません。

以下のサンプル例ではアクション名に「contact」を指定していますが、これが正しい使い方かどうかは不明です。公式ページのサンプルでは「homepage」というアクション名が使われています。

HTML(クライアント)側の実装

HTML(クライアント)側の実装では、reCAPTCHA の API(ライブラリ)を読み込み、API のメソッドを使ってトークンを取得します。v2 とは異なりウィジェットの表示などの記述は必要ありません。そしてフォームを使って post メソッドなどでサーバへトークンを送信します。

公式ページのガイドには以下のようなクライアント側の実装例が掲載されています。

最初に reCAPTCHA の API(ライブラリ)を読み込んで、ライブラリが読み込まれた際に実行される grecaptcha.ready() で grecaptcha.execute() を使ってトークンを取得しています。

1行目の API(ライブラリ)の読み込みでパラメータ render に v3 のサイトキーを指定すると、ページの右下に reCAPTCHA v3 の画像が表示されます。

<script src="https://www.google.com/recaptcha/api.js?render=サイトキー"></script>
<script>
grecaptcha.ready(function() {
  grecaptcha.execute('サイトキー', {action: 'アクション名'}).then(function(token) {
     ...
  });
});
</script> 

上記の場合、ページを読み込んですぐにトークンを取得しているので、2分後にはこのトークンの有効期限が切れてしまいます。

クライアント側の実装例

以下はフォームの中に送信ボタンが1つのみの HTML の例です。

11行目の API の読み込みではサイトを登録した際に取得したサイトキーを「render=サイトキー」で指定します。API の読み込みは head 要素内でも以下の位置でも大丈夫です。ただし、grecaptcha.ready() より前で読み込む必要があります。

14行目の grecaptcha.execute() メソッドは、Promise を使って Google のサーバからトークンを非同期的に取得します。その際に、サイトキーとアクション名を指定する必要があります。

grecaptcha.execute() メソッドがトークンの取得に成功すると、then() メソッドの第1引数の関数 function(token) のパラメータにトークン(token)が渡されます。

サーバ側にトークンを送信するため、hidden 属性を指定した id="my_token" の input 要素の値 token_input.value にトークン(token)を設定しています。

この要素(8行目)の name 属性は recaptcha_response なのでサーバ側では $_POST[ 'recaptcha_response' ] でトークンの値を取得します。

action: のアクション名は9行目の hidden 属性を指定した input 要素(name 属性は action)の値を設定しています。

送信ボタンをクリックすると input 要素の値に設定したトークンとアクション名がサーバへ送信されます。サーバ側では $_POST[ 'recaptcha_response' ] でトークンの値、$_POST[ 'action' ] でアクション名を取得できます。

但し、以下の場合、トークンの有効期限はページを開いてから2分間です(送信ボタンをクリックしてからではありません)。

<html lang="ja">
<head>
<title>reCAPTCHA v3 Sample 1</title>
</head>
<body>
  <form id="rc_form" method="post">
    <button type="submit">送信</button>
    <input type="hidden" name="recaptcha_response" id="my_token">
    <input type="hidden" name="action" value="アクション名">
  </form>
<script src="https://www.google.com/recaptcha/api.js?render=サイトキー"></script>
<script>
grecaptcha.ready(function() {
  grecaptcha.execute('サイトキー', {action:'アクション名'}).then(function(token) {
    //id="my_token" の input 要素の値にトークンを設定
    var token_input = document.getElementById('my_token');
    token_input.value = token;
  });
});
</script>
</body>
</html>

また、必要に応じて catch() メソッドを使って then() メソッド内などで発生したエラーを捕捉することもできます。

<script>
grecaptcha.ready(function() {
  grecaptcha.execute('サイトキー', {action:'アクション名'}).then(function(token) {
    //id="my_token" の input 要素の値にトークンを設定
    var token_input = document.getElementById('my_token');
    token_input.value = token;
  }).catch(e => console.log(e));  //例外処理を追加
});
</script>
</body>
</html>

<!--
但し、サイトキーなどが間違っている場合は、以下のようにエラーを捕捉することができないようです。
recaptcha__ja.js:291 Uncaught (in promise) Error: Invalid site key or not loaded in api.js: xxxxxxxxxxxxxxxxxx
    at Array.<anonymous> (recaptcha__ja.js:291)
    at Array.<anonymous> (recaptcha__ja.js:220)
    at recaptcha_v3_html_01.php:行番号
-->
送信ボタンをクリックした際にトークンを取得

前述の例の場合、ページの読み込みの際に grecaptcha.execute() メソッドを実行してトークンを取得しているため、トークンの有効期限はページを開いてから2分間です。

以下は、送信ボタンをクリックした際にトークンを取得する例で、トークンの有効期限は送信ボタンをクリックしてから2分間になります。

以下ではフォーム要素にイベントハンドラを設定して送信時にトークンを取得しています。

但し、そのまま送信を実行してしまうと非同期処理の grecaptcha.execute() メソッドはサーバからトークンを取得できません(トークンを取得する前にフォームの送信が完了してしまいます)。

そのため、preventDefault() でデフォルトの動作(フォームの送信)を停止し、その間にトークンを取得して input 要素を生成し必要な値(トークンとアクション名)を設定します。

その後 submit() でフォームを送信しています。

<html lang="ja">
<head>
<title>reCAPTCHA v3 Sample 2</title>
</head>
<body>
  <form id="rc_form" method="post">
    <button type="submit">送信</button>
  </form>
<script src="https://www.google.com/recaptcha/api.js?render=サイトキー"></script>
<script>
var rc_form = document.getElementById('rc_form');
//フォーム要素にイベントハンドラを設定
rc_form.onsubmit = function(event) {
  //デフォルトの動作(送信)を停止
  event.preventDefault();
  //トークンを取得
  grecaptcha.ready(function() {
    grecaptcha.execute('サイトキー', {action: 'アクション名'}).then(function(token) {
      var token_input = document.createElement('input'); //input 要素を生成
      token_input.type = 'hidden';
      token_input.name = 'g-recaptcha-response';
      token_input.value = token; //トークンを値に設定
      rc_form.appendChild(token_input);
      var action_input = document.createElement('input'); //input 要素を生成
      action_input.type = 'hidden';
      action_input.name = 'action';
      action_input.value = 'アクション名';  //アクション名を値に設定
      rc_form.appendChild(action_input);
      rc_form.submit();  //フォームを送信
    });
  });
}
</script>
</body>
</html>

以下は同じことを jQuery を使って行う例です。※ jQuery を読み込む必要があります。

<html lang="ja">
<head>
<title>reCAPTCHA v3 Sample 3</title>
</head>
<body>
  <form id="rc_form" method="post">
    <button type="submit">送信</button>
  </form>
<script src="https://www.google.com/recaptcha/api.js?render=サイトキー"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script><!-- jQuery の読み込み -->
<script>
jQuery(function($){
  //フォーム要素に submit イベントを設定
  $('#rc_form').submit(function(event) {
    //デフォルトの動作(送信)を停止
    event.preventDefault();
    //トークンを取得
    grecaptcha.ready(function() {
      grecaptcha.execute('サイトキー', {action: 'アクション名'}).then(function(token) {
        //input 要素を生成して値にトークンを設定
        $('#rc_form').prepend('<input type="hidden" name="g-recaptcha-response" value="' + token + '">');
        //input 要素を生成して値にアクション名を設定
        $('#rc_form').prepend('<input type="hidden" name="action" value="アクション名">');
        //unbind で一度 submit のイベントハンドラを削除してから submit() を実行
        $('#rc_form').unbind('submit').submit();
      });;
    });
  });
})
</script>
</body>
</html>

参考にさせていただいたサイト:Example of How to Add Google reCAPTCHA v3 to a PHP Form

JavaScript の検証でエラーがない場合にトークンを取得

以下は、JavaScript の検証でエラーがある場合は error-js クラスが付与された要素が生成されるスクリプトを使用している例です。

この場合、JavaScript の検証でエラーがあるかどうかの判定(23行目)は grecaptcha.execute() メソッド内で行い、エラーがない場合にのみトークンを取得しています。

フォームの部品や検証用の JavaScript は省略しています(関連項目)。また、以下では onsubmit の代わりに addEventListener を使っています。

<form id="rc_form" method="post">
  <!-- フォーム部品(省略) -->
  <button type="submit">送信</button>
</form>
<!--  検証用の JavaScript の読み込み(省略) -->
<script src="formValidation.js"></script>
<script src="https://www.google.com/recaptcha/api.js?render=サイトキー"></script>
<script>
//id が rc_form の form 要素を取得
const rc_form = document.getElementById('rc_form');
//フォーム要素に submit イベントハンドラを設定
rc_form.addEventListener('submit', (e) => {
  //デフォルトの動作(送信)を停止
  e.preventDefault();
  //アクション名を定義
  const action_name = 'contact';
  //トークンを取得
  grecaptcha.ready(function() {
    grecaptcha.execute('サイトキー', {action: action_name}).then(function(token) {
      //エラー(error-js クラス)の要素を取得
      const errorElem = document.querySelector('.error-js');
      //エラー(error-js クラス)の要素が存在しなければ
      if(errorElem === null) {
        const token_input = document.createElement('input'); //input 要素を生成
        token_input.type = 'hidden';
        token_input.name = 'g-recaptcha-response';
        token_input.value = token; //トークンを値に設定
        rc_form.appendChild(token_input);
        const action_input = document.createElement('input'); //input 要素を生成
        action_input.type = 'hidden';
        action_input.name = 'action';
        action_input.value = action_name;  //アクション名を値に設定
        rc_form.appendChild(action_input);
        rc_form.submit();  //フォームを送信
      }
    });
  });
});
</script> 

PHP を使った検証(v3)

サーバー側ではフォームから送信されたトークンとシークレットキーを使って reCAPTCHA の API にリクエストを送信してその返り値を解析します。

トークンを確認するための API Request の送信方法やその返り値(API Response)の取得方法は v2 と同じです(Verifying the user's response)。

API の URL:https://www.google.com/recaptcha/api/siteverify(v2 と同じ)

トークンの値は前述の HTML 実装例の場合、 form 要素の隠し属性を指定した input 要素に設定されていて POST メソッドでサーバに送られるので、その input 要素の name 属性が g-recaptcha-response であれば $_POST[ 'g-recaptcha-response' ] で取得できます。

以下は cURL 関数を使って API からレスポンスを取得してする例です。レスポンスは JSON 形式になっているので json_decode() でデコードします。

//cURL セッションを初期化
$ch = curl_init();
// curl_setopt() により転送時のオプションを設定
// API の URL の指定
curl_setopt($ch, CURLOPT_URL,"https://www.google.com/recaptcha/api/siteverify");
// POST メソッドを使う
curl_setopt($ch, CURLOPT_POST, true );
// API パラメータの指定
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
  'secret' => $secretKey,   //シークレットキー
  'response' => $_POST[ 'g-recaptcha-response' ]  //トークン
)));
// curl_execの返り値を文字列にする
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//転送を実行してレスポンスを $api_response に格納
$api_response = curl_exec($ch);
//セッションを終了
curl_close($ch);

//レスポンスの $json(JSON形式)をデコード
$result = json_decode( $api_response );

以下は file_get_contents() を使う例です。

//API Request URL
$url = 'https://www.google.com/recaptcha/api/siteverify';
//パラメータを指定
$data = array(
  'secret' => $secretKey,   //シークレットキー
  'response' =>  $_POST[ 'g-recaptcha-response' ] //トークン
);
$context = array(
  'http' => array(
    'method'  => 'POST', // POST メソッドを使う
    'header'  => implode("\r\n", array('Content-Type: application/x-www-form-urlencoded',)),
    'content' => http_build_query($data)
  )
);
//上記パラメータを指定して file_get_contents でレスポンスを取得
$api_response = file_get_contents($url, false, stream_context_create($context));

//レスポンス(JSON形式)をデコード
$result = json_decode(  $api_response );

API の URL にパラメータを指定して、以下のように(POST メソッドを指定せずに)レスポンスを取得することもできますが、正しい方法かはわかりません。

$api_url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . $secretKey . '&response=' . $_POST[ 'g-recaptcha-response' ];
$api_response = file_get_contents( $api_url );

レスポンス

API からの返り値(レスポンス)は以下のような JSON データ形式になっています。

<?php echo $api_response; ?>
{
  "success": true,
  "challenge_ts": "2020-03-21T04:53:48Z",
  "hostname": "webdesignleaves.localhost",
  "score": 0.9,  //スコア
  "action": "contact"  // アクション名
}
//前述の例のレスポンス($api_response)を echo で出力した例

v2 と v3 の違い

v2 の場合は、レスポンスの success プロパティが true ならトークンは有効と判定できましたが、v3 の場合はレスポンスの score(スコア)の値を基に判定する必要があります。

v3 の場合、success プロパティは単にレスポンスを取得できたかどうかを表すもので、BOT かどうかの判定ではありません。

BOT かどうかの判定は score で判定します。score が1に近いほど BOT ではない(人間による操作である)可能性が高く、0に近いほど BOT の可能性が高いことになります。

また、判定の際にはアクション名(action)も検証することが推奨されていますが action(アクション名)が異なっていても success プロパティは true になるので注意が必要です。

reCAPTCHA v3:Interpreting the score

PHP の実装例(v3)

以下は、送信ボタンをクリックすると PHP で検証するサンプルです。スコアが 0.5 以上で且つアクション名が一致していれば[検証結果]に「合格:$result->score :スコアの値」と表示します。

PHP 側の実装ではシークレットキーを使用するので、シークレットキーは別ファイル(以下の例では recaptcha_vars.php)に保存して require などを使って読み込むようにしています。

シークレットキーを記述したファイルはパブリックからアクセスできない場所に配置するか、.htacess などを使って外部からアクセスできないようにします。

recaptcha_vars.php
<?php
// reCAPTCHA v3 サイトキー
define('V3_SITEKEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx');
// reCAPTCHA v3 シークレットキー
define('V3_SECRETKEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx');

以下が上記サンプルのコードです。

この例では POST メソッドでトークンを自分自身(現在のファイル)に送信して検証しているので、HTML では form 要素の action 属性は省略しています(HTML5 の場合)。

判定は success が true でアクション名が一致し、且つスコアが 0.5 以上の場合は合格としています。必要に応じてスコアの判定の値を変更します(41行目)。

<?php
// サイトキーとシークレットキーを記述したファイルの読み込み
require 'libs/recaptcha_vars.php';
// reCAPTCHA サイトキー
$siteKey = V3_SITEKEY;
// reCAPTCHA シークレットキー
$secretKey = V3_SECRETKEY;
//reCAPTCHA トークン
$token = isset( $_POST[ 'g-recaptcha-response' ] ) ? $_POST[ 'g-recaptcha-response' ] : NULL;
//reCAPTCHA アクション名
$action = isset( $_POST[ 'action' ] ) ? $_POST[ 'action' ] : NULL;

$result_status = ''; // 結果を表示する文字列を初期化

// トークンとアクション名が取得できれば
if ( $token && $action) {

  //cURL セッションを初期化(API のレスポンスの取得)
  $ch = curl_init();
  // curl_setopt() により転送時のオプションを設定
  //URL の指定
  curl_setopt($ch, CURLOPT_URL,"https://www.google.com/recaptcha/api/siteverify");
  //HTTP POST メソッドを使う
  curl_setopt($ch, CURLOPT_POST, true );
  //API パラメータの指定
  curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
    'secret' => $secretKey,
    'response' => $token
  )));
  //curl_execの返り値を文字列にする
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  //転送を実行してレスポンスを $api_response に格納
  $api_response = curl_exec($ch);
  //セッションを終了
  curl_close($ch);

  //レスポンスの $json(JSON形式)をデコード
  $result = json_decode( $api_response );

  //判定
  if ( $result->success && $result->action === $action && $result->score >= 0.5) {
    //success が true でアクション名が一致し、スコアが 0.5 以上の場合は合格
    $result_status = '合格: $result->score : ' . $result->score;
    // 合格した場合の処理(メールの送信など)を実行(または結果を変数に入れて、その変数を使って処理を分岐するなど)
  } else {
    // 上記以外の場合は 不合格
    $result_status = '不合格';
    // 不合格の場合の処理(エラーを表示するなど)を実行
  }
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<title>PHP を使った検証(reCAPTCHA v3)1</title>
</head>
<body>
  <h1>PHP を使った検証(reCAPTCHA v3)1</h1>
  <form id="rc_form" method="post">
    <button type="submit">送信</button>
  </form>
  <div>
    <p>[検証結果]</p>
    <p><?php echo $result_status; ?></p>
  </div>
<script src="https://www.google.com/recaptcha/api.js?render=<?php echo $siteKey; ?>"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>
jQuery(function($){
  $('#rc_form').submit(function(event) {
    event.preventDefault();
    var action_name = 'contact'; //アクション名
    grecaptcha.ready(function() {
      grecaptcha.execute('<?php echo $siteKey; ?>', { action: action_name }).then(function(token) {
        $('#rc_form').prepend('<input type="hidden" name="g-recaptcha-response" value="' + token + '">');
        $('#rc_form').prepend('<input type="hidden" name="action" value="' + action_name + '">');
        $('#rc_form').unbind('submit').submit();
      });;
    });
  });
})
</script>
</body>
</html>

以下は reCAPTCHA v3 を使ったコンタクトフォームのサンプルです。以下のサンプルでは実際にはメールは送信されません。

サンプルを別ページで開く

コードの全文を別ページで開く

関連ページ:お問い合わせフォームの設置/reCAPTCHA v3 を使う

関連ページ:コンタクトページの作り方/reCAPTCHA v3 を使う

ライブラリを使う(v3)

API に直接リクエストするのではなく、reCAPTCHA のライブラリを使って(ライブラリが API にリクエストを送信して)検証することもできます。

ライブラリのインストールやメソッドについては「ライブラリを使う(v2)」を御覧ください。

以下が実装例です。

9行目ではインストールしたライブラリを読み込んでいます。この例では Composer を使ってライブラリをインストールしているので Composer の autoload.php を指定しています。直接ダウンロードした場合は、reCAPTCHA の autoload.php を指定する必要があります。

トークンを検証するには、シークレットキーを渡して reCAPTCHA のインスタンスを生成し、そのメソッドを使って判定の条件を設定して verify() メソッドで検証します。

v3 の場合は、ホスト名の他に「アクション名」の確認と「スコア」の閾値を設定するメソッドを使って条件を設定します。

そして検証結果のオブジェクト($resp)のメソッド isSuccess() を使って判定します。

以下の場合、isSuccess() は「ホスト名」や「アクション名」がマッチして「スコア」が 0.5 以上であれば true を返します。

<?php
// サイトキーとシークレットキーを記述したファイルの読み込み
require 'libs/recaptcha_vars.php';
// reCAPTCHA サイトキー
$siteKey = V3_SITEKEY;
// reCAPTCHA シークレットキー
$secretKey = V3_SECRETKEY;
// reCAPTCHA ライブラリの読み込み
require 'reCAPTCHA/vendor/autoload.php';

//reCAPTCHA トークン
$token = isset( $_POST[ 'g-recaptcha-response' ] ) ? $_POST[ 'g-recaptcha-response' ] : NULL;
//reCAPTCHA アクション名
$action = isset( $_POST[ 'action' ] ) ? $_POST[ 'action' ] : NULL;
// 結果を表示する文字列を初期化
$result_status = '';

// トークンとアクション名が取得できれば
if ( $token && $action) {

  //インスタンスを生成
  $recaptcha = new \ReCaptcha\ReCaptcha($secretKey);
  $resp = $recaptcha->setExpectedHostname($_SERVER['SERVER_NAME']) //ホスト名の確認
                    ->setExpectedAction($action) //アクション名の確認
                    ->setScoreThreshold(0.5) //スコアの閾値の設定(この場合 0.5 以上)
                    ->verify($token, $_SERVER['REMOTE_ADDR']);

  if ($resp->isSuccess() ) {
    // 検証を通過した場合の処理(メールの送信など)
    $result_status = 'Verified(検証通過)!';
  } else {
    // 検証を通過しなかった場合の処理(エラーの表示など)
    $errors = $resp->getErrorCodes();
    $result_status = 'Failed(失敗) ';
  }
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<title>PHP を使った検証 2(reCAPTCHA v3 ライブラリを使用する場合)</title>
</head>
<body>
  <form id="rc_form" method="post">
    <button type="submit" class="btn btn-primary">送信</button>
  </form>
  <div>
    <p>[検証結果]</p>
    <p><?php echo $result_status; ?></p>
    <p>[var_dump($resp)]</p>
    <pre><?php if(isset($resp)) {var_dump($resp);} ?></pre>
    <p>[print_r($errors)]</p>
    <pre><?php if(isset($errors)) {print_r($errors);} ?></pre>
  </div>
<script src="https://www.google.com/recaptcha/api.js?render=<?php echo $siteKey; ?>"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>
jQuery(function($){
  $('#rc_form').submit(function(event) {
    event.preventDefault();
    var action_name = 'contact'; //アクション名
    grecaptcha.ready(function() {
      grecaptcha.execute('<?php echo $siteKey; ?>', { action: action_name }).then(function(token) {
        $('#rc_form').prepend('<input type="hidden" name="g-recaptcha-response" value="' + token + '">');
        $('#rc_form').prepend('<input type="hidden" name="action" value="' + action_name + '">');
        $('#rc_form').unbind('submit').submit();
      });;
    });
  });
})
</script>
</body>
</html>

以下は検証を通過した場合の検証結果($resp)を var_dump() で表示した例です。

object(ReCaptcha\Response)#5 (7) {
  ["success":"ReCaptcha\Response":private]=>
  bool(true)  //成功
  ["errorCodes":"ReCaptcha\Response":private]=>
  array(0) {
  }
  ["hostname":"ReCaptcha\Response":private]=>
  string(25) "webdesignleaves.localhost"
  ["challengeTs":"ReCaptcha\Response":private]=>
  string(20) "2020-03-22T02:28:57Z"
  ["apkPackageName":"ReCaptcha\Response":private]=>
  NULL
  ["score":"ReCaptcha\Response":private]=>
  float(0.9)
  ["action":"ReCaptcha\Response":private]=>
  string(7) "contact"
}

以下は setScoreThreshold(0.95) とスコアの閾値を 0.95 にした場合(検証を通過しない場合)の検証結果($resp)の例です。

object(ReCaptcha\Response)#6 (7) {
  ["success":"ReCaptcha\Response":private]=>
  bool(false)  //失敗
  ["errorCodes":"ReCaptcha\Response":private]=>
  array(1) {
    [0]=>
    string(23) "score-threshold-not-met"  //スコアを満たさない
  }
  ["hostname":"ReCaptcha\Response":private]=>
  string(25) "webdesignleaves.localhost"
  ["challengeTs":"ReCaptcha\Response":private]=>
  string(20) "2020-03-22T02:29:00Z"
  ["apkPackageName":"ReCaptcha\Response":private]=>
  NULL
  ["score":"ReCaptcha\Response":private]=>
  float(0.9)
  ["action":"ReCaptcha\Response":private]=>
  string(7) "contact"
}