Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails 7: has_secure_password利用時のauthenticate_byメソッド(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

注: この#43765はRailsのmainブランチにマージされていますが、現時点ではまだ7-0-stableブランチに含まれていません。

追記(2023/09/26): #43765はその後7-0-stableブランチには含まれず、7.1.0beta1で初めて含まれました。また、パスワードが空の場合の修正も7.1.0beta1に含まれました。

Rails 7: has_secure_password利用時のauthenticate_byメソッド(翻訳)

急速に成長するデジタル世界で誰もが気がかりなものといえば、セキュリティです。

フィッシングや違法行為がしばしば発生する今日のサイバーエコシステムでは、セキュリティの必要性は避けては通れません。

認証(authentication)は、多くのWebサイトがセキュリティ実現のために採用している基本的なしくみです。認証を手短に説明すると、ユーザー(顧客)がWebサイトのさまざまなコンテンツにアクセスするときに、「ユーザー名(またはメールアドレス)」「パスワード」でログインすることを要求するというものです。

改修前

たとえばCustomerモデルにemail属性があるとします。ここに"richard_roe@example.com"というメールアドレスを持つレコードが1件あります。このcustomerレコードは以下のように作成できます。

Customer.create(name: "Richard Roe", email: "richard_roe@example.com", password: "password123")

注意: 以下のコードスニペットはcustomerの認証に使われます。このコードは、マッチするメールアドレスがcustomerに存在しない場合は早期にreturnします早めに処理が終了します。

Customer.find_by(email: "richard_roe@example.com")&.authenticate("password123")

上のコードスニペットは、指定のメールアドレスを持つcustomerが存在するかどうかを攻撃者が判断できてしまうため、タイミングベースの列挙攻撃に対して脆弱です。

専用のパスワード管理アプリケーションを使わず、同じパスワードを複数のWebサイトで使いまわしてしまう人は跡を絶ちません。

攻撃者はデータベースにアカウントが存在することを確認すると、インターネット上に流出したデータベースの中から該当するメールアドレスのパスワードを探してログインを試みます。アカウントのメールアドレスが事前にわかっている場合、攻撃者は標的を絞り込んだブルートフォース攻撃やフィッシング(スピアフィッシング)攻撃も試せます。

改修後

Rails でauthenticate_byというクラスメソッドが新たに導入されます。

Customer.authenticate_by(email: "richard_roe@example.com", password: "password123")

authenticate_byは、指定のパスワード属性をダイジェスト化し、タイミングベースの列挙攻撃を軽減するのに役立ちます。このメソッドは、パスワード以外の属性を利用してレコードを検索し、見つかったレコードをパスワード属性で認証します。

認証が成功した場合はそのレコードを返し、失敗した場合はnilを返します。

class Customer < ActiveRecord::Base
  has_secure_password
end

Customer.create(name: "Richard Roe", email: "richard_roe@example.com", password: "password123")

Customer.authenticate_by(email: "richard_roe@example.com", password: "password123").name
# => "Richard Roe"

Customer.authenticate_by(email: "richard_roe@example.com", password: "invalid_password")
# => nil

Customer.authenticate_by(email: "invalid@example.com", password: "password123")
# => nil

このメソッドは、属性のセットに少なくとも1個のパスワード属性と1個の非パスワード属性が含まれている必要があります。それらの属性がない場合はArgumentError`エラーが発生します。

Customer.authenticate_by(email: "richard_roe@example.com")
# => ArgumentError

Customer.authenticate_by(password: "password123")
# => ArgumentError

変更内容について詳しくは、#43765(および#43779#43958#43997)をご覧ください。

関連記事

Rails 7: スキーマキャッシュが遅延読み込み可能になった(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。