#全体像の把握
Cookiesはブラウザのみで保存する
new_tokenでランダムな値を生成し、それを仮想的な属性である.rememberと.remember_confirmationに一時的に保存し(合わせてremember_token)、その値をハッシュ化して、DBのremember_digestに保存。
さらに、remember_tokenはuser.idとともにcookiesにも焼き付ける。cookiesに置いておくことで、ユーザー情報を保持することができる。user_idを復号化することで、Userの情報を復元し、そこからDBのremember_digestを取得する。一方で、焼き付けておいたremember_tokenと照合して、認証を行う。
#DBへdigestを保存
1 remember_tokenをハッシュ化させた値を保存しておく場所を作成
rails generate migration add_remember_digest_to_users remember_digest:string
rails db:migrate
2 ランダムな文字列を作成
SecureRandom.urlsafe_base64
3 仮想的な属性にアクセスする部分の実装(setterとgetter)
has_secure_passwordがやってくれていたことを自分で実装する必要がある
u.passwordに代入できるが、データベースに保存しないという機能(仮想的な属性にアクセス)
今はu.rememberとすることはできない
def remember= (token)
@remember = token
end
def remember
@remember
end
よく使うのでRubyで実装されており、この一行に短縮できる
attr_accessor :remember
4.保存
def remember
# Userがログイン状態を保持したいと思い、チェックボックスにチェック(remember me)し、
# ログインが成功した時に呼び出されるメソッドremember()から
self.remember_token = User.new_token
# 新しいトークンを発行し、このメソッドを呼び出したオブジェクト(self)のremember_tokenに入れる
# remember_tokenは次のリクエストが来るまでしか有効ではない
update_attribute(:remember_digest, User.digest(remember_token))
# new_tokenは開発側が作った値なので、これまでのようにvaliをかける必要がないので
# update_attribute()を使う
# update_attribute(name,value) = (属性名・カラム?,値・更新後の)
# remember_tokenがハッシュ化された値が、DBに保存
end
#認証
さらに認証のためにuserのid情報を暗号化したものもcookiesに入れておき、そこからuser_idを復号化し、find_byによりユーザーオブジェクトを取得し、athenticateでユーザーの平文(remember_token)とDBのdigestと照合する
authenticateでuser情報が必要な理由は、どのUserのremember_tokenか指定する必要があるから
bcryptの認証
remember_tokenとdigestが一致したらtrue
実際の認証の部分、図での一番最後の部分
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
このメソッドをsessionコントローラのcreateアクションのloginの後に書くことで、ログインと同時に、remember_tokenをDBへ保存したり、cookiesへ保存が行われる
def remember(user)
user.remember # DBへremember_digestを保存
cookies.permanent.signed[:user_id] = user.id
# userのidも暗号化してcookiesに入れる
# 文法はあまり考えないでいい
cookies.permanent[:remember_token] = user.remember_token
# remember_tokenをcookiesに入れる
end
sessionを使って復元することを試みて、sessionで無理なら、cookiesを使って復元する
def current_user
#sessionを使った復号化
if user_id = session[:user_id]
# user_idというローカル変数にsessionの値を代入
# 代入されたローカル変数の値を条件式で評価、比較演算子(==)ではない
# 評価されるので数字かnilが返る、数字ならtrue
@current_user ||= User.find_by(id: user_id) # ここまでは同じ
#cookiesを使った復号化、cookiesが入ってなかったらnilで終了
elsif (user_id = cookies.signed[:user_id])
# 復号化されて実際のuser_idになる
user = User.find_by(id: user_id)
# 数字が返ってきたら、user_idをfind_byで取得し、Userオブジェクトに引っ掛ける
if user && user.authenticated?(cookies[:remember_token])
#Userのcookiesにあるremember_tokenとremember_digestを照合
log_in user #login状態に
@current_user = user #User情報の復元
end
end
end
このメソッドをどこで呼び出しているのか、分かりづらいが
このメソッドは第8章でしたように、ここで呼び出される
使われているのは、_header.htmlで、ログインしている時としていない時の振る舞いを変えるところ
def logged_in?
!current_user.nil?
end
第8章ではsessionが切れているかどうかでログインしているか確かめる意味合いでしかなかったが、ここではcookiesを使った復号化を試みる場所という機能も与えられている。
このlogged_inメソッドがheaderで使われているので、ユーザーがこのwebサイトの何らかのページに行く時には必ずcurrent_userメソッドも呼び出されると考えられる。
ログイン機構の理解
まずログイン時にセッションとクッキーでユーザー情報を保持する部分
次に、ユーザーがログアウトせずにページを離れて、またやってきた場合、そのユーザー情報を渡す(復号化)部分の構築がポイントだと思う
Userを忘れる
rememberメソッドと対になる
def forget
update_attribute(:remember_digest, nil) #remember_digestをnilに
end
remember(user)の対
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
def log_out
forget(current_user) #上のメソッドを呼んでいる
session.delete(:user_id)
@current_user = nil
end
連続ログアウト問題
二つのデバイスでログインした状態で、片方をログアウトさせると、もう片方がログアウトする時にはlog_outメソッドのforget(current_user)のcurrent_userがいないのでnilが渡されErrorとなる
解決策として、log_outメソッドを呼び出す時、nilじゃないことを確認する
destroyアクションで
log_out if logged_in?
ログアウトするできるのはログインしている時だけ
2回目以降のログアウトはログインしていないので、log_outが実行されず、redirectだけ実行
二つのバグとlog_in_asのテストはよくわからない