8
6

More than 5 years have passed since last update.

Rails で、複数 ID に対して OR 条件で BASIC 認証をかける方法

Posted at

前提

  • Rails の話
  • 下記のような2組の ID/PW ペアが存在するとする
    • user1 / pass1
    • user2 / pass2

やりたいこと

  • あるアクションに対して、 user1 または user2 だけがアクセスできるような BASIC 認証をかけたい

結論

以下のように実装すればよい。

class FooController < ApplicationController
  before_action :multiple_authenticate

  def index
  end

  protected

  def multiple_authenticate
    authenticate_or_request_with_http_basic do |username, password|
      (
        ActiveSupport::SecurityUtils.secure_compare(username, 'user1') &
        ActiveSupport::SecurityUtils.secure_compare(password, 'pass1')
      ) | (
        ActiveSupport::SecurityUtils.secure_compare(username, 'user2') &
        ActiveSupport::SecurityUtils.secure_compare(password, 'pass2')
      )
    end
  end
end

もちろん、ID/PW をハードコードしているところは適宜セキュアにすること(環境変数を使う、secret.yml を使うなど)。

解説

まず、「やりたいこと」で示した「OR 条件による BASIC 認証」を実現する標準機能はどうやら存在しないようでした。
仕方ないので自前で実装するわけですが、ここで普通に BASIC 認証するときに使う http_basic_authenticate_with の実装を参考にします。ソースはこちら (https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/http_authentication.rb#L71-L81) 。

このメソッドは、直下で定義されている authenticate_or_request_with_http_basic というメソッド(名前が似ていてややこしい)を使って before_action を設定しているだけのようです。authenticate_or_request_with_http_basic は、

  • リクエストヘッダに BASIC 認証情報が含まれていなければ 401 を返す(ID / PW を聞く)
  • リクエストヘッダに BASIC 認証情報が含まれていればブロックを評価し、
    • truthy なら処理を続行
    • falsy なら 401 を返す(ID / PW を聞く)

という挙動をします。ということは、authenticate_or_request_with_http_basic のブロック内で user1 または user2 の ID/PW と一致するかどうかをチェックすればよい、ということになります。

ここで、http_basic_authenticate_with のソースに目を戻します(繰り返しになりますが、メソッド名がややこしいので混乱しないよう注意してください)。この実装を見ると、ID/PW の比較に変わったメソッドを使っています。

# This comparison uses & so that it doesn't short circuit and
# uses `secure_compare` so that length information
# isn't leaked.
ActiveSupport::SecurityUtils.secure_compare(name, options[:name]) &
ActiveSupport::SecurityUtils.secure_compare(password, options[:password])

ここでは、セキュリティのために以下の2つの工夫がこらされています。

  • 文字列の比較に secure_compare を使っている
    • コメントにも書かれていますが、これによって比較対象の文字長がバレないようになっています。
    • 文字列比較が「そもそも文字長が一致してなければ即エラーを返す」という実装になっている場合、比較処理がすぐ終わるかどうかで文字長を推理できてしまいます。いわゆるタイミング攻撃というものです。
    • 実装を見てみると、与えられた文字列を SHA256 でハッシュ化してから比較しています。つまり常に 256 文字同士の比較になるため、セキュアというわけです。
    • 参考: https://github.com/rails/rails/commit/fa487763d98ccf9c3e66fdb44f09af5c37a50fe5
  • AND 演算に & を使っている
    • コメントにも書かれていますが、これによって短絡評価がされないようになっています。
    • 短絡評価されてしまうと、name が正しいときと間違っているときとで処理時間に差が出てしまい、ここでもタイミング攻撃のリスクが発生します。
    • 参考: https://github.com/rails/rails/commit/17e6f1507b7f2c2a883c180f4f9548445d6dfbda

これに倣うと、今回実装する複数 ID/PW ペアに対する OR 条件での認証も、

  • 文字列の比較に secure_compare を使う
  • OR 条件に | を使う

という方針で実装すればよさそうです。その実装例が上記で示したものとなっています。

8
6
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
8
6