Closed4

Railsチュートリアル「11.3.1 authenticated?メソッドの抽象化」の演習2の`authenticated?`メソッドが`true`にならず、`false`になる時の対処

MASAMASA

事象

Railsチュートリアル「11.3.1 authenticated?メソッドの抽象化」の演習2のauthenticated?メソッドがtrueにならず、falseになる。

MASAMASA

デバッグ

ソースコードにbinding.pryを入れて、デバッグする。
(※ binding.pryを利用するには、gem 'pry-rails'が必要)

[3] pry(main)> user.authenticated?(:activation ,"$2a$12$aGjTyNj/2nIQmddu9Jpz2u7wtQU9mipnhftMoO/jcBV92A1B0ocNW")

From: /home/masa/environment/RailsTutorial/sample_app2/app/models/user.rb:39 User#authenticated?:

    36: def authenticated?(attribute, token)
    37:   digest = send("#{attribute}_digest")
    38:   return false if digest.nil?
 => 39:   binding.pry
    40:   BCrypt::Password.new(digest).is_password?(token)
    41: end

[1] pry(#<User>)> 

まずは、digesttokenの値を確認。

[2] pry(#<User>)> digest
=> "$2a$12$aGjTyNj/2nIQmddu9Jpz2u7wtQU9mipnhftMoO/jcBV92A1B0ocNW"
[3] pry(#<User>)> token
=> "$2a$12$aGjTyNj/2nIQmddu9Jpz2u7wtQU9mipnhftMoO/jcBV92A1B0ocNW"
[4] pry(#<User>)> digest == token
=> true

digesttokenは同等である。
次に、BCrypt::Password.new(digest).is_password?(token)を確認

[5] pry(#<User>)> BCrypt::Password.new(digest).is_password?(token)
=> false

is_password?は、==のシュガーシンタックス(書き換え)なので、
BCrypt::Password.new(digest)(token)の値が同等ではないということを示している。

そこで、BCrypt::Password.new(digest)digestを比較してみる。

[6] pry(#<User>)> BCrypt::Password.new(digest)
=> "$2a$12$aGjTyNj/2nIQmddu9Jpz2u7wtQU9mipnhftMoO/jcBV92A1B0ocNW"
[7] pry(#<User>)> digest
=> "$2a$12$aGjTyNj/2nIQmddu9Jpz2u7wtQU9mipnhftMoO/jcBV92A1B0ocNW"
[8] pry(#<User>)> BCrypt::Password.new(digest) == digest
=> false

見た目は同じように見えるが、同等ではない。

次に、BCryptがどんな処理をしているかを知るために、BCryptの公式リファレンスを確認してみる。

MASAMASA

公式リファレンスの確認

ここで、BCryptの公式リファレンスを確認してみると、
クラスメソッドにcreateがあるので、そちらで試してみる。

[9] pry(#<User>)> BCrypt::Password.create(digest).is_password?(token)
=> true

trueが返ってきた。
クラスメソッドのcreateと、インスタスメソッドinitialize(newメソッドで呼び出されるメソッド)の違いを公式リファレンスから確認する。

Class Method Summary
 .create(secret, options = {}) ⇒ Object
  Hashes a secret, returning a BCrypt::Password instance.
Instance Method Summary
 #initialize(raw_hash) ⇒ Password constructor
  Initializes a BCrypt::Password instance with the data from a stored hash.

翻訳すると、

クラスメソッドの概要
 .create(secret, options = {}) ⇒ オブジェクト
  秘密をハッシュし、BCrypt::Password インスタンスを返します。
インスタンスメソッドの概要
 #初期化(raw_hash) ⇒ パスワードのコンストラクタ
  保存されたハッシュからのデータでBCrypt::Passwordインスタンスを初期化します。

createメソッドは、引数をハッシュ化して、BCrypt::Password インスタンスを返す。
initializeメソッドは、(ハッシュ化された)引数でBCrypt::Passwordインスタンスを初期化する。

なるほど、、、わからん。。
これ以上の詳細は理解が難しそうなので、詳しい人に聞けるときに聞くことにします。

以上、ありがとうございました。

参考

BCryptの公式リファレンス

MASAMASA

解決

こちらのサイトに回答が載っていたので、それを参考に実行してみたところ、
authenticated?メソッドはtrueとなった。
以下、その手順。

db/seeds.rbを参考にuserオブジェクトを作成。

[2] pry(main)> user = User.new(name: 'kappy-',
[2] pry(main)*   email: 'kappy-@nikoniko.com',  
[2] pry(main)*   password: 'kappy-',  
[2] pry(main)*   password_confirmation: 'kappy-',  
[2] pry(main)*   activated: true,  
[2] pry(main)*   activated_at: Time.zone.now)
=> #<User:0x000055f08655eca0
 id: nil,
 name: "kappy-",
 email: "kappy-@nikoniko.com",
 created_at: nil,
 updated_at: nil,
 password_digest: "[FILTERED]",
 remember_digest: nil,
 admin: false,
 activation_digest: nil,
 activated: true,
 activated_at: Sun, 17 Jan 2021 00:51:53 UTC +00:00>

remember_tokenに新しいトークンを作成。

[3] pry(main)> user.remember_token = User.new_token
=> "5T5kP80PvNUcHKNKVwu4Fw"

:remember_digestを設定。

[4] pry(main)> user.update_attribute(:remember_digest, User.digest(user.remember_token))
   (0.3ms)  BEGIN
  User Create (3.3ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "remember_digest", "activation_digest", "activated", "activated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING "id"  [["name", "kappy-"], ["email", "kappy-@nikoniko.com"], ["created_at", "2021-01-17 01:02:09.022858"], ["updated_at", "2021-01-17 01:02:09.022858"], ["password_digest", "$2a$12$U8dlalylRNa67F2w7gMyW.XHBvR1XEe1htUU/WXwq1kxs90yQT.mG"], ["remember_digest", "$2a$12$C7f0BKY6CXjZ/tkKKRLBienkHBP20L8X5ComTnW.3ibI2.PNwRJ/y"], ["activation_digest", "$2a$12$FIKkeRneEDG7uBISj6.ebOLnzLOeh7IzKhWks3URS9cJQzEYfTRNG"], ["activated", true], ["activated_at", "2021-01-17 00:51:53.410158"]]
   (1.3ms)  COMMIT
=> true

設定された情報を確認。

[5] pry(main)> user
=> #<User:0x000055f08655eca0
 id: 104,
 name: "kappy-",
 email: "kappy-@nikoniko.com",
 created_at: Sun, 17 Jan 2021 01:02:09 UTC +00:00,
 updated_at: Sun, 17 Jan 2021 01:02:09 UTC +00:00,
 password_digest: "[FILTERED]",
 remember_digest: "$2a$12$C7f0BKY6CXjZ/tkKKRLBienkHBP20L8X5ComTnW.3ibI2.PNwRJ/y",
 admin: false,
 activation_digest: "$2a$12$FIKkeRneEDG7uBISj6.ebOLnzLOeh7IzKhWks3URS9cJQzEYfTRNG",
 activated: true,
 activated_at: Sun, 17 Jan 2021 00:51:53 UTC +00:00>
[6] pry(main)> user.remember_token
=> "5T5kP80PvNUcHKNKVwu4Fw"
[7] pry(main)> user.remember_digest
=> "$2a$12$C7f0BKY6CXjZ/tkKKRLBienkHBP20L8X5ComTnW.3ibI2.PNwRJ/y"

authenticated?メソッドを使って、トークン/ダイジェストの組み合わせで認証が成功することを確認。

[8] pry(main)> user.authenticated?(:remember, "5T5kP80PvNUcHKNKVwu4Fw")
=> true

trueになりました。

自分がこれを確認できなかったのは、Railsチュートリアルの内容をちゃんと理解していないことが原因でした。
精進精進。

参考

【11章】Ruby on Railsチュートリアル演習まとめ&解答例【11.3 アカウントを有効化する】

このスクラップは2021/01/17にクローズされました