チワワかわいいブログ

RUNTEQでrails勉強する日々の記録

2020/12/15 railsチュートリアル9章

今回やること

cookiesを使ってセッション情報を保存する
データベースとブラウザを使ってやりとりする
ブラウザに暗号化したデータを保存(cookies)
cookiesのパスワードとデータベースのデータが一致するかを確認する
これまでは自分の覚えているパスワードがデータベースと一致しているかを確認していた

具体的に

・記憶トークン用のカラムを用意 ・ランダムな文字列を生成しハッシュ化する(token)
・ハッシュ化した値をDBに保存する(remember_digestに保存)
・ブラウザのcookiesに暗号化したIDとTokenを保存する
・cookiesのIDを使ってユーザーをDBから検索 ・tokenを認証し同一ならセッションを復元する

ランダムな文字列を生成しハッシュ化する(token)
SecureRandom.urlsafe_base64

ランダムな文字列を作ってくれる
これを使ってメソッドを作成

仮想的な属性を作る

has_secure_passwordを使ってパスワードを保存するとき、passwordカラムはないのにpassword属性を扱うことができた
ユーザーがpasswordを入力→authenticateでハッシュ化される→DBのpassword_digestと一致するか確認して認証
同じように仮想の属性を作りたい

attr_accessor :remember_token

クラスの中ではインスタンス変数と同じようにデータを保存できるけど、DBとは連携しない

ハッシュ化した値をDBに保存する→cookiesのIDを使ってユーザーをDBから検索
def User.new_token
    SecureRandom.urlsafe_base64
end

def remember
    self.remember_token = User.new_token #ここでのselfはrememberメソッドのレシーバー(userインスタンス)のこと
    update_attribute(:remember_digest,User.digest(remember_token))
end

remember_tokenにランダムな文字列を保存、User.digestメソッドでハッシュ化して、update_attributeでremember_digestに保存

ブラウザのcookiesに暗号化したIDとTokenを保存する
cookies[:remember_token] = { value:   remember_token, expires: 20.years.from_now.utc }

cookiesには、値(:remember_token)と期限(expires: 20.years.from_now.utc)を入れないといけない。

cookies.permanent[:remember_token] = remember_token

で書き込むこともできる。
IDは

cookies.permanent.signed[:user_id] = user.id

signedで暗号化できる。
rememberメソッドでremember_digestにキーを保存
cookiesメソッドでcookiesにキー(暗号化ずみ、ハッシュ化していない)を保存
というメソッドを作る

def remember(user)
    user.remember
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
end

このメソッドをログイン時に使うことでDB、ブラウザにユーザーごとの認証データを保存することができる

tokenを認証し同一ならセッションを復元する
 def authenticated?(remember_token)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
end

全然わからない
実際に使うときのコード見ると

user.authenticated?(cookies[:remember_token])

これ見てちょっとわかった気になる
このメソッドのremember_tokenは引数としてメソッドを使うときに渡すもので、さっきのattr_accessorの奴は別物
remember_digestは、レシーバーのDBにあるやつ
まだわからないので実験

# passwordは"chihuahua"

irb(main):001:0> d = User.first.password_digest   #変数dに代入
   (0.4ms)  SELECT sqlite_version(*)
  User Load (0.1ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> "$2a$12$MQCAWCJf52lLohOv.GkyyOhga5dL.1P0oxst1.9BRHWJv4c6H29Ly"
irb(main):002:0> d == "chihuahua"  #もちろん一致しない
=> false
irb(main):004:0> d2 = BCrypt::Password.new(d)  #謎メソッドを実行
=> "$2a$12$MQCAWCJf52lLohOv.GkyyOhga5dL.1P0oxst1.9BRHWJv4c6H29Ly"  #ぱっと見変わらなそう
irb(main):005:0> d == d2  #メソッド実行前と実行後は一致
=> true
irb(main):006:0> d2 == "chihuahua"  #なぜか一致!
=> true
irb(main):007:0> d == "chihuahua"  #d==d2はtrueなのにこれはfalse
=> false

結果メソッドが何してるのか全然わからなかったけど、
とりあえずBCrypt::Password.newしたdigestの値は平文と比較できるんだってことだけ理解。

 # 記憶トークンcookieに対応するユーザーを返す
  def current_user
    if (user_id = session[:user_id]) #セッション情報があれば
      @current_user ||= User.find_by(id: user_id) #@ccurent_userはセッションに情報がある人
    elsif (user_id = cookies.signed[:user_id]) #セッション情報がなければcookiesに情報があるか、cookiesメソッドで持ってくる
      user = User.find_by(id: user_id)  #cookies.signed[:user_id]のユーザーを代入
      if user && user.authenticated?(cookies[:remember_token]) #ユーザーがいて、remember_digestの情報がただしければ
        log_in user
        @current_user = user
      end
    end
  end

if (user_id = session[:user_id])
=>比較ではなく代入、user_idに値があるかないかでtrue/falseを返す