この記事はデベロッパー アドボケート、Doug Stevenson による The Firebase Blog の記事 "Guard Your Web Content from Abuse with reCAPTCHA and Firebase" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。

ウェブを使ったことがある方なら、reCAPTCHA が表示され、本当に人間が操作しているのか確かめるサイトを見たことがあるのではないでしょうか。たとえば、goo.gl 短縮 URL 生成ツールを使う場合、次のような reCAPTCHA を通過しなければ短縮リンクを作成できません。

ウェブサイト エンジニアは、サイトをスパムやボットによる不正使用から守りつつ、人間による正規の利用を許可するために、このようなことを行っています。では、なぜこのような保護が必要なのでしょうか。時間や保存容量の面で高価なバックエンド コードがあり、実際のウェブユーザーのみにしかアクセスしてほしくないなどの可能性が考えられます。

ウェブサイトをお持ちの皆さんは、サービスを保護するために reCAPTCHA を使うことができます。また、Firebase Hosting を使ってサイトを構築している場合、Cloud Functions for Firebase を利用すれば、簡単に reCAPTCHA を組み込んで安全で拡張可能な reCAPTCHA 検証バックエンドを提供できます。

この記事では、数ステップで完了する基本的な組み込み方法を紹介します。後でこの方法を拡張して、皆さんのサイトに reCAPTCHA を組み込んでいただくことも可能です。このチュートリアルでは、すでにウェブ開発や Firebase ConsoleFirebase CLI について、いくらかの経験があることを前提としています。

1. コンソールで Firebase プロジェクトを作成する

Firebase コンソールを開き、新しいプロジェクトを作成します。このプロジェクトには、課金は必要ありません。実験はすべてクレジット カードを登録せずに行うことができます。プロジェクトを作成したら、コンソールで行うことはもうありません。

2. プロジェクトのコードを格納するディレクトリを設定する

Firebase CLI から、プロジェクトを作成したときに利用した Google アカウントにログインします。
$ firebase login

次に、プロジェクトのルート ディレクトリを作成し、初期化します。
$ mkdir my_project
$ cd my_project
$ firebase init

firebase init を実行するときは、hosting と functions の両方を選択します。プロジェクトを選ぶよう尋ねられたら、先ほど作成したプロジェクトを選択します。その他のプロンプトでは、すべてデフォルトを選びます。最終的に、ウェブ コンテンツ用の public フォルダと、バックエンド コード用の functions フォルダを含むディレクトリ構造ができあがります。

次に、Cloud Functions バックエンドで reCAPTCHA の検証に使用するいくつかのモジュールを npm を使ってインストールします。reCAPTCHA API では、バックエンドから検証するために HTTP リクエストを作成する必要があります。これには、 request モジュールと request-promise モジュールを使用します。次のようにして、これらのモジュールをインストールします。
$ cd functions
$ npm install request request-promise

package.json ファイルには、firebase-functions と firebase-admin に加えて、上記の 2 つのモジュールが新しく追加されます。

3. ウェブ デプロイメントをテストする

次のように、deploy コマンドを実行し、ウェブ コンテンツをデプロイできることを確認します。
$ firebase deploy --only hosting

コマンドが終了すると、新しいウェブサイトのパブリック URL が表示されます。これは、次のような形式になっています。
✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/your-project/overview
Hosting URL: https://your-project.firebaseapp.com

your-project は、コンソールからプロジェクトを作成したときに割り当てられた固有の ID です。Hosting の URL をブラウザに貼り付けると、「Firebase Hosting Setup Complete」というページが表示されるはずです。

4. reCAPTCHA API キーを取得する

reCAPTCHA を動作させるには、2 つの API キーが必要になります。1 つはウェブ クライアント用、もう 1 つはサーバー API 用です。これらは、reCAPTCHA 管理パネルから取得できるため、そのページに移動します。新しいサイトを作って名前を付け、[reCAPTCHA V2] を選択します。ドメイン欄には、Firebase Hosting の完全なサイト名(例: 「your-project.firebaseapp.com」)を入力します。

登録が完了したら、Site キーSecret キーを入手できます。Site キーはフロントエンド HTML に使用し、Secret キーは Cloud Functions にホストするバックエンドで使用します。

5. reCAPTCHA ページを追加する

次に、reCAPTCHA を表示する新しい HTML ページを追加します。プロジェクトの public ディレクトリに、recaptcha.html という名前で reCAPTCHA を表示する新しい HTML ファイルを追加します。新しいファイルには、次の内容を直接コピーして貼り付けてください。
<html>
  <head>
    <title>Firebase + reCAPTCHA</title>
    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
    <script type="text/javascript">
    function dataCallback(response) {
        console.log("dataCallback", response)
        window.location.href = "/checkRecaptcha?response=" + encodeURIComponent(response)
    }
    function dataExpiredCallback() {
        console.log("dataExpiredCallback")
    }
    </script>
  </head>
  <body>
    <div class="g-recaptcha"
      data-sitekey="PASTE_YOUR_SITE_KEY_HERE"
      data-callback="dataCallback"
      data-expired-callback="dataExpiredCallback"/>
  </body>
</html>

body の中に「g-recaptcha」クラスの div があります。ここで最初に行う必要があるのは、reCAPTCHA の Site キーを div の data-sitekey 属性の値にコピーすることです。上部にある最初のスクリプトが読み込まれると、この div は自動的に reCAPTCHA の UI に変換されます。詳しくは、こちらのドキュメントをご覧ください。

もう 1 度 firebase deploy を実行してから、Hosting の URL の「/recaptcha.html」に移動すると、すぐに確認できます。しかし、まだ reCAPTCHA を使ってはいけません。検証できるようにするには、バックエンドのコードが必要です。

このページの JavaScript コードでは、dataCallbackdataExpiredCallback という 2 つの関数が定義されています。この 2 つの関数は、reCAPTCHA が成功した場合と、ユーザーが一定時間内に成功させることができなかった場合のコールバックになっており、div から参照されています。

重要なのは、dataCallback でブラウザを /checkRecaptcha というパスの別の URL にリダイレクトし、response という名前でパラメータを渡している点です。この文字列は reCAPTCHA が生成するもので、ランダムな文字列のように見えます。

ウェブサイトには /checkRecaptcha というパスはまだ存在しません。そこで、reCAPTCHA からのレスポンス文字列を Cloud Function を使って検証するようにします。

6. reCAPTCHA のレスポンスを検証する Cloud Function を作る

プロジェクトの functions ディレクトリにある index.js ファイルを編集します。このファイルには、いくつかのサンプルコードが書かれていますが、それは削除して構いません。その場所に、次の JavaScript コードを貼り付けます。
const functions = require('firebase-functions')
const rp = require('request-promise')

exports.checkRecaptcha = functions.https.onRequest((req, res) => {
    const response = req.query.response
    console.log("recaptcha response", response)
    rp({
        uri: 'https://recaptcha.google.com/recaptcha/api/siteverify',
        method: 'POST',
        formData: {
            secret: 'PASTE_YOUR_SECRET_CODE_HERE',
            response: response
        },
        json: true
    }).then(result => {
        console.log("recaptcha result", result)
        if (result.success) {
            res.send("You're good to go, human.")
        }
        else {
            res.send("Recaptcha verification failed. Are you a robot?")
        }
    }).catch(reason => {
        console.log("Recaptcha request failure", reason)
        res.send("Recaptcha request failed.")
    })
})

ここで最初に行う必要があるのは、登録サイトで取得した reCAPTCHA の Secret キーを「PASTE_YOUR_SECRET_CODE_HERE」と書かれた場所に貼り付けることです。

(鋭い読者の皆さんは、ドキュメントには reCAPTCHA API エンドポイントのホストが、「www.google.com」と書かれているにもかかわらず、ここでは「recaptcha.google.com」になっていることに気づくかもしれません。これは問題ありません。Spark プランでこの呼び出しを行うためには、ここに記載したように、recaptcha.google.com を使う必要があります。このホストは、Cloud Functions からの外部トラフィックの宛先としてホワイトリストに登録されています)

このコードは、HTTPS 関数を定義しています。これがトリガーされると、クエリ文字列として受信したレスポンスを検証するために、reCAPTCHA API への別の HTTPS リクエスト(request-promise モジュールを使用しています)が作成されます。3 通りのケースがあり、それに応じてクライアントに 3 通りのレスポンスが返されることに注意してください。具体的には、以下のいずれかになります。
  1. reCAPTCHA の検証が成功する(ユーザーは人間である)
  2. reCAPTCHA が失敗する(ロボットの可能性がある)
  3. API の呼び出しが失敗する

重要になるのは、 すべてのケースでクライアントにレスポンスを送り返すことです。そうしないと、Function はタイムアウトし、Firebase Console のログにエラー メッセージが出力されます。

次のコマンドを実行して、この新しい関数をデプロイします(同時に、ウェブ コンテンツもデプロイされます)。
$ firebase deploy

出力から、関数が URL に割り当てられたことがわかります。これは、次のような URL になります。
https://us-central1-your-project.cloudfunctions.net/checkRecaptcha

ここに表示されているホストは、ウェブ コンテンツが格納されているホストとは明らかに異なります。ここで実際にやりたいのは、次のような URL で、お使いのウェブホストを指定して関数を参照することです。
https://your-project.firebaseapp.com/checkRecaptcha

これができれば、関数がウェブサイトの一部であるように見えます。Firebase Hosting と Cloud Functions を利用すると、これを実現できます。

7. Hosting の URL を Cloud Function にマッピングするためのリライトを追加する

プロジェクトのルート ディレクトリにある firebase.json ファイルを編集し、その内容に次の JSON 設定を貼り付けます。
{
  "hosting": {
    "public": "public",
    "rewrites": [
      {
        "source": "/checkRecaptcha",
        "function": "checkRecaptcha"
      }
    ]
  }
}

詳しくはドキュメントをご覧いただくとして、ここでは、リライト用のセクションを新しく追加しています。具体的には、URL パス /checkRecaptcha にアクセスした際に、先ほど functions/index.js ファイルに貼り付けた checkRecaptcha 関数が呼び出されるようにしています。

recaptcha.html の JavaScript コードで、ユーザーが reCAPTCHA を通過した際は、このパスにリダイレクトするようにしたことを思い出してください。そのため、reCAPTCHA の処理を終えたユーザーは、実質的にこの関数に送られることになります。

最後にもう 1 度デプロイを行い、すべてのファイルを Firebase に送信します。
$ firebase deploy

8. reCAPTCHA をテストする

Hosting の URL の /recaptcha.html に移動し、reCAPTCHA を通過してみましょう。たとえば、何枚かの写真が表示され、車や道を判別することが求められます。人間としてふさわしい操作を行って reCAPTCHA を通過すると、HTML 内の JavaScript によって関数にリダイレクトされます。その関数は、サーバーを使って本当に人間による操作であるかを検証し、その結果、「You're good to go, human.」というメッセージが表示されます。

ここで紹介した Cloud Functions for Firebase で reCAPTCHA を使うサンプルは、おそらく実際のウェブサイトで利用するものより、かなりシンプルになっています。reCAPTCHA のレスポンスを関数に送る方法はいくつかあり、当然ながら、皆さんはユーザーにメッセージを表示するよりももっと役立つことをしたいと思うでしょう。しかしこの例は、ウェブ コンテンツをボットによる不正使用から保護する最初の一歩になるはずです。



Reviewed by Eiji Kitamura - Developer Relations Team