Google Apps Script(GAS) で reCaptcha 認証をする

Google Apps Script(GAS) で reCaptcha 認証をする

前記事「はてなブログの Googleフォームに reCaptcha を導入」に書きましたように当サイトのお問い合わせフォームメールにスパムがくるようになりましたので reCaptcha を導入しようと、その記事ではクライアント側だけのバリデーションを試しましたが、やはりこれでスパムをはじくのは難しく、なんとかサーバー側、この場合は Google Apps Script(GAS) ですので、GAS で認証チェックできないかとやってみました。

可能です。成功しました!

reCaptcha 認証とは

詳しくは、「はてなブログの Googleフォームに reCaptcha を導入」をご覧ください。

  • reCaptcha でサイトを登録し、サイトキーとシークレットキーを取得する
  • フォーム内に <div class="g-recaptcha" data-sitekey="your_site_key"></div> を仕込む
  • ユーザが reCaptcha をチェックするとサイトキー(your_site_key)を使ったトークンが作成される
  • フォームデータとともにトークンをサーバーに送る
  • サーバー側でトークンとシークレットキーを使って認証チェックを行う
  • 認証の結果によって振り分ける

といった流れかと思います。

で、クライアント側で reCaptcha がチェックされたか、つまりトークンが作成されたかを調べればボットを防げるかと思いましたが、考えてみれば、ボットはいちいちそのサイトに来てスパムを送るわけではなく、フォームの action属性や input要素を取得して自動化しているわけですから、クライアントの Javascript なんかじゃ防げないですね。

Google Apps Script で reCAPTCHA 認証

ということで、Google Apps Script(GAS)で reCAPTCHA 認証をするためには、

  • トークンを GAS に送る
  • GAS で認証する
  • 可能なら結果をクライアントに返す

という過程を経れば認証が可能になります。

クライアント側

考え方は汎用のものですが、下のコードははてなブログにお問合せフォームを設置するためのものです。「はてなブログの Googleフォームに reCaptcha を導入」をご覧ください。

<script>
function checkRecaptcha() {
  var response = grecaptcha.getResponse();
  if(response.length == 0) { 
    //トークンなし
    document.getElementById('recaptcha-error').innerHTML = "reCaptchaにチェックを入れてください";
    return false;
  }
  else { 
    //トークンあり
    document.myForm.elements['下のフォーム内のinput要素のhidden型'].value = response;
    document.myForm.submit();
  }
}


function showThxMessage(){
   var email = document.myForm.elements['下のフォーム内のメールのinput要素'].value;
   if(email !== ''){
       var thxDiv = document.getElementById('thxMessage');
       thxDiv.getElementsByTagName('span')[0].innerHTML = email;
       document.myForm.reset();
       document.getElementById('formWrapper').style.display = 'none';
       thxDiv.style.display = 'block';
   }
}
</script>
<div id="formWrapper">
    <form action="https://docs.google.com/forms/d/e/グーグルフォームのアクセス先" method="post" name="myForm" target="dummyIframe" onsubmit="return checkRecaptcha();">
        <label>メールアドレス</label>
        <p style="font-size:.8em;color:#999;">※配信可能なメールアドレスを入力してください. 自動返信メールがエラーとなる場合はサイト管理人へも配信されません.</p>
        <input type="email" name="メールのinput要素" value="" required pattern="^(([-\w\d]+)(\.[-\w\d]+)*@([-\w\d]+)(\.[-\w\d]+)*(\.([a-zA-Z]{2,5}|[\d]{1,3})){1,2})$" />
        <label>お問い合わせ内容</label>
        <textarea required name="お問い合わせ内容のinput要素"></textarea>
        <div class="g-recaptcha" data-sitekey="reCaptchのサイトキー"></div>
        <div style="margin-top:10px; color:red;" id="recaptcha-error"></div>
        <input type="hidden" name="recaptcha名などで作成したinput要素" value="">
        <input style="margin-top:10px;" type="submit" value="送信">


        <iframe name="dummyIframe" style="display:none;" onload="showThxMessage();"></iframe>
    </form>
</div>
<div id="thxMessage" style="display:none;">
    お問い合わせありがとうございました。<br />
    <span style="color:red;"></span> 様あてのメールでお答えいたします。<br /><br />
    また自動返信メールをお送りいたしましたのでご確認ください。<br />
    メールが届いていない場合はお答えメールも届きませんので、迷惑メールとなっていないかなどご確認ください。<br /><br />
    --<br />
    IMUZA.com
</div>

フォーム内に hidden型の input要素を仕込んでおき、ユーザーが reCaptchaにチェックを入れることで作成されたトークンを grecaptcha.getResponse() で hidden型に入れて GASに送ります。

サーバー(GAS)側

同じく verifyCaptcha() は汎用のものですが、スクリプト自体は上記クライアント側に対応したものです。

参考記事
reCAPTCHA with Google Apps Script – Google Apps Script Tutorial

//Secret key
var secret = 'reCaptcha のシークレットキー';


function verifyCaptcha(arg){
  var payload = {
    'secret' : secret,
    'response': arg
  }
  var url = 'https://www.google.com/recaptcha/api/siteverify';
  var resp = UrlFetchApp.fetch(url, {
    payload : payload,
    method : 'POST'
  }).getContentText();
  return JSON.parse(resp).success;
}


function submitForm(e){
  var itemResponses = e.response.getItemResponses();
  for (var i = 0; i < itemResponses.length; i++) {
    var question = itemResponses[i].getItem().getTitle();
    var answer = itemResponses[i].getResponse();
    if (question == 'メールアドレス') var userMail = answer;
    if (question == 'お問い合わせ内容') var userMessage = answer;
    if (question == 'recaptcha') var recaptcha = answer;
  }


  var resp = verifyCaptcha(recaptcha);


  if(resp)
  {
  // 自動返信メール
  var subject = 'お問い合わせ内容確認';
  var body = 'お問い合わせありがとうございました。\n'
    + 'このメールは自動返信メールです。\n'
    + '後ほど ' + userMail + ' 様あてにご連絡いたします。\n\n'
    + '-- お問い合わせ内容 ------------------------------------\n\n'
    + userMessage + '\n\n'
    + '----------------------------------------------------------------\n\n'
    + '--\n'
    + 'IMUZA.com\n'
    + 'http://www.imuza.com\n\n';
  GmailApp.sendEmail(userMail, subject, body, {name: 'IMUZA.com'});


  // 通知メール
  subject = 'お問い合わせがありました';
  body = 'お問い合わせがありました。\n'
    + userMail + ' あてに返信してください。\n\n'
    + '-- お問い合わせ内容 ------------------------------------\n\n'
    + userMessage + '\n\n'
    + '----------------------------------------------------------------\n\n'
    + '--\n'
    + 'IMUZA.com\n'
    + 'https://www.imuza.com\n\n';
  GmailApp.sendEmail('通知を送りたいメールアドレス', subject, body, {name: 'IMUZA.com'});
  }
}

もちろん、IMUZA.com などとなっているところは参考として残してあるだけですのでご自身のサイト名や URLにに変更してください。

結果と問題点

これでスパムの場合は確認メールや通知メールを送信しなくなります。ただし、Google Formには回答として保存されます。

その結果をみますと、やはりスパムの場合はトークンに何も入っていません。

問題としては、回答が保存されてしまうことと、ボットには関係ないことですが、「可能なら結果をクライアントに返す」ができなく、クライアント側でありがとうメッセージへ画面遷移してしまうことです。

とりあえずは成功ですが、この際もう少し Google Apps Script を調べてみようと思います。

詳解! GoogleAppsScript完全入門 ~GoogleApps & G Suiteの最新プログラミングガイド~

詳解! GoogleAppsScript完全入門 ~GoogleApps & G Suiteの最新プログラミングガイド~

  • 作者:高橋宣成
  • 出版社/メーカー: 秀和システム
  • 発売日: 2017/12/23
  • メディア: 単行本