Nust.jsで制作中のサイトにGoogle reCAPTCHAを設置して不正なアクセスから防御する

今、「ウェブサイト制作をぐっと楽にするウェブサービス」を制作している。

ナウいウェブサービスを目指しているので、ログインフォームやアカウント作成フォームにはreCAPTCHAを設置して、ロボットによる不正なアクセスから防御したい。

今回、初めてreCAPTCHAを使うので、ざっと概要を確認してみた。

  • バージョンが1~3まである
  • クライアントサイドとサーバサイドの両方で処理が必要
  • クライアントサイドでtokenを取得
  • サーバサイドでtokenをAPIに送信して結果を取得する

バージョンの違いについては、以下のとおり。

  • バージョン1
    画像に埋め込まれた文字をタイプするやつ(面倒くさい)
  • バージョン2
    ブロック状に並んだ画像から、提示された条件を満たす画像を選択するやつ(画像によっては選択しづらい)
  • バージョン3 – New!
    ユーザーの行動パターンを捕捉してAIで自動判定するナウいやつ。ユーザーは何もしなくてよい!

1と2については、イライラさせられた経験を誰もがお持ちだと思う。とてもナウいとは言えない。当然、ユーザーが何もしなくてよいバージョン3を採用。

サーバーサイドで返却されるjsonデータに、scoreという0~1の数値を返すパラメータが含まれており、この数値を見て、人間なのかロボットなのかを判断してねという仕組みらしい。1に近いほど人間である可能性が高いけれど、どこを閾値にするかは、サービスの性質に合わせて各自判断してね、目安としては、0.5以上であれば概ね人間だと思うよ、ということみたい。

ここまで把握できたら、粛々と作業を開始しよう。

目次

サイトキーとシークレットキーを取得

まずは、以下のサイトから、サイトキーとシークレットキーを取得しておく。利用するドメインを登録する際に、開発用にlocalhostも追加しておこう。

http://www.google.com/recaptcha/admin

クライアントサイド

クライアントサイドについては、 npmにいろいろなライブラリが登録されていたけど、ここは素直に @nuxtjs チームの @nuxtjs/recaptcha モジュールを使えば良さそう。

npm i @nuxtjs/recaptcha

続いて、nuxt.config.jsに設定を書いていく。複数のページで使用する予定だけど、設定が一箇所で済むので助かる。Nuxtは本当に便利で好き。

{
  modules: [
    '@nuxtjs/recaptcha',
  ],

  recaptcha: {
    hideBadge: true, // Hide badge element
    siteKey: 'xxxxxxxx site key xxxxxxx',    // Site key for requests
    version: 3     // Version
  },
}

@nuxtjs/recaptchaモジュールのgithubにサンプルが用意されているので、それを参考にコーディングしていく。

  1. mountedでモジュールを初期化
  2. onSubmitでtokenを取得して、認証データにtokenを添えてサーバに送信
export default {
  ...
  ...
  async mounted() {
    await this.$recaptcha.init()
  },
  methods: {
    async onSubmit() {
      try {
        const token = await this.$recaptcha.execute('login')
        // ログイン処理
        ...  tokenをログイン情報と共にサーバに送信する
        ...
      } catch (error) {
        console.log('Login error:', error)
      }
    }
  }
}

tokenは取得から2分で無効になってしまうので、使用する直前に取得しなくてはならない。onSubmitで取得すると、レスポンス的にどうかと心配したけど、特に問題なさそうなので、onSubmitで取得する方法でいくことに。

また「こいつロボットくさいぞ」と判定された場合の処理も追加する必要がある。結果はサーバサイドで取得するので、サーバからの戻り値を見て、何らかのメッセージを表示するようにしたい。その点を心にしっかり留めて、サーバサイドのコーディングに進もう。

サーバサイド

今回のウェブサービスでは、サーバサイドは nodo.js + express を利用したログインのコードがすでに設置済みなので、それにreCAPTCHAの処理を追加していく。

ログインのミドルウェアに、reCAPTCHAのコードを追加していく。

// expressの初期化等は省略

app.post('/auth/login', async (req, res, next) => {
  // reCAPTCHAの処理
  // トークンが送信されているかチェックする
  if (!req.body.recaptchaToken) {
    return next({ code: 400, msg: 'recaptchaToken is required' })
  }
  const options = {
    url: 'https://www.google.com/recaptcha/api/siteverify',
    method: 'POST',
    form: {
      secret: CAPTCHA_SECRET,
      response: req.body.recaptchaToken
    },
    json: true
  }
  request(options, (error, response, body) => {
    if (error) {
      return next({ code: 500, msg: 'recaptcha api error' })
    }
    // 閾値により判定する
    if (body.score < 0.5) {
        return next({ code: 403, msg: '正しい手順で操作されませんでした。' })
    }
    // 以下でログイン処理を行う
    ...
    ...
  })
})


// エラー処理
app.use((err, _req, res, _next) => {
  res.status(err.code || 500).send(err.msg || 'Internal Server Error')
})

既存のコードを書き換えることなく追加できた。素晴らしい。

再びクライアントサイド

上記のコードの場合、クライアントサイドでは、response.dataでエラーメッセージを受け取れるので、例外処理の部分を以下のようにすればOK。

} catch (error) {
  this.errorMsg = error.response ? error.response.data : 'ログインに失敗しました!'
}

完成!

思いのほか簡単にナウいログインフォームが完成した。
問合せフォームや新規アカウント申込フォームなどにも、同様の方法で導入していこう。

シェアする

  • このエントリーをはてなブックマークに追加