LoginSignup
10
13

More than 5 years have passed since last update.

devise に CAPTCHA 認証を追加する方法

Last updated at Posted at 2013-12-11

2014/12/11 追記

新しくなった reCAPTCHA が良さそうだったので、それに対応した。


やりたいことは、Twitter や Facebook みたいに数回ログインに失敗したら、そのユーザーでログインするのに CAPTCHA 認証を通さないといけない機能。
devise とかいうクソ gem 1 を使ってしまったせいで苦労したので、書き留めておく。

なお、使う CAPTCHA は reCAPTCHA

devise の Lockable の設定

config/initializers/devise.rb
Devise.setup do |config|
  # ...

  # ==> Configuration for :lockable
  config.lock_strategy = :failed_attempts
  config.unlock_strategy = :none
  config.maximum_attempts = 2 # お好きな値に

  #...
end

CAPTCHA の表示

アカウントロックされているかどうかを知るだけでも工夫がいる。

ログインの検証を行ってるのは、Devise::SessionsController#createwarden.authenticate! だが、warden.authenticate に変えたところで、アカウントロックされているユーザーのモデルは取得できない。

最初は、warden の Strategy を拡張する方法や、custom failure を使う方法とか小難しいこと考えたけど、Devise::Strategies#validatefail! に渡している認証失敗メッセージを確認することにした。

app/controllers/devise/sessions_controller.rb
class SessionsController < Devise::SessionsController
  helper_method :access_locked?

  # アカウントロックでエラーになったか調べる
  def access_locked?
    # Devise::Models::Lockable#unauthenticated_message の戻り値が入ってる
    warden.message == :locked
  end
end

あとは、View に表示させる。

app/views/devise/sessions/new.html.haml
-# CAPTCHA を表示させたいところに追加
- if access_locked?
  render "devise/sessions/recapture"
app/views/devise/sessions/_recapture.html.haml
%script{:type => "text/javascript", :src => "//www.google.com/recaptcha/api.js", :defer => true, :async => true}

.g-recaptcha{:"data-sitekey" => your_site_key}

まあ自分で書かなくても、ruby-recaptcha なんかの gem を使ってもいいと思います。

CAPTCHA 認証とロック解除

warden の Strategy を拡張する。

lib/devise/strategy/captcha_authenticatable.rb
require 'cgi'
require 'net/http'
require 'uri'
require 'devise/strategies/authenticatable'

module Devise
  module Strategies
    # Devise 標準の DatabaseAuthenticatable に、reCAPTCHA 認証の機能を追加。
    class CaptchaAuthenticatable < Authenticatable
      def authenticate!
        resource  = valid_password? && mapping.to.find_for_database_authentication(authentication_hash)
        encrypted = false

        if validate(resource){ encrypted = true; validate_resource(resource) }
          resource.after_database_authentication
          success!(resource)
        end

        mapping.to.new.password = password if !encrypted && Devise.paranoid
        fail(:not_found_in_database) unless resource
      end

      # ロックされたユーザーの CAPTCHA 認証と、パスワードの検証を行う。
      def validate_resource(resource)
        if resource.access_locked?
          return false unless validate_recaptcha
          resource.unlock_access! # CAPTCHA 認証が通ったらロック解除

          # パスワードまで合ってないとロック解除しないとか、
          # いろんな要件があると思うが、このメソッド内に記述すればよい
        end
        resource.valid_password?(password)
      end

      protected

      # CAPTCHA の検証を行います。
      def validate_recaptcha
        # CAPTCHA のパラメータがある場合のみ検証する
        return false unless params.has_key?("g-recaptcha-response")

        res = Net::HTTP.post_form(URI.parse("https://www.google.com/recaptcha/api/siteverify"),
                                  { :secret     => your_secret_key,
                                    :remoteip   => CGI.escape(request.remote_ip),
                                    :response   => CGI.escape(params["g-recaptcha-response"]) })
        results = JSON.parse(res.body)
        Rails.logger.info("reCAPTCHA: #{results}")
        results["success"]
      end
    end
  end
end

Warden::Strategies.add(:captcha_authenticatable, Devise::Strategies::CaptchaAuthenticatable)

前述した ruby-recaptcha なんかを使ってるなら、validate_recaptcha の代わりに、その gem で用意されたメソッドを使えばよい。

拡張した Strategy を使うように devise を設定する。

config/initializers/devise.rb
require 'devise/strategies/captcha_authenticatable'

Devise.setup do |config|
  # ...

  # ==> Warden configuration
  config.warden do |manager|
    manager.default_strategies(:scope => :user).unshift :captcha_authenticatable
  end

  #...
end

2013/12/26 追記

lib/devise/hooks/lockable.rb でリセット処理を行っているので、下記処理は不要だった。

ログイン成功した時に、ログイン失敗のカウントをリセットする。
app/model/user.rb
class User < ActiveRecord::Base

  # override Devise::Models::DatabaseAuthenticatable#after_database_authentication
  def after_database_authentication
    # lib/devise/hooks/lockable.rb の処理と重複していたのでコメントアウト
    #unlock_access! if failed_attempts > 0
  end
end

以上。


1. 個人の主観が入っています。

10
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
13