rails チュートリアル 9章 複数のブラウザでのログインログアウト処理が難しかった
8章まではなんとなく理解でき覚えておけそうだったのだが9章から複雑になってきたので長考したところや忘れそうなことはブログでまとめようと思います。
複数のブラウザで開いてどちらもログイン状態の場合に片方がログアウトしたあともう片方のログインしている方でログアウトしないでブラウザを閉じたあとサイドブラウザを立ち上げるとエラーになってしまうバグ解説が難しかった
(app/helpers/sessions_helper.rb)
# 記憶トークンcookieに対応するユーザーを返す def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) log_in user @current_user = user end end end
前提知識
current_user解説
セッションにuser_idが入っているとログイン状態
クッキーは永続ログイン化のために使用する
firefoxがログアウト以下のメソッドを実行する
(app/models/user.rb)
# ユーザーのログイン情報を破棄する def forget update_attribute(:remember_digest, nil) end
このメソッドは永続化ログインに使用されるクッキーに登録されている記憶トークンとDBに登録されている記憶ダイジェストのうちの一つ、DBに登録されている記憶ダイジェストを消去するメソッドである
このあとログインしているchromeのブラウザを閉じる
するとセッションは消える(session[:user_id]がnilになる)
そしてchromeブラウザを開きサイドアプリケーションにアクセスする
この時cookiesは残り続けているため以下の行を実行してしまう(記事一番最初のメソッドの一部)
(app/helpers/sessions_helper.rb)
elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) log_in user @current_user = user end
そして以下が実行される(app/models/user.rb)
# 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end
ここで問題になるのはremember_digest
firefoxでログアウトした時にremember_digestはnilになってしまったので例外処理を吐き出してしまう
問題解決策(app/models/user.rb)
# 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end
黄色くなっている行を追加する
テスト(user_test.rb)
test "authenticated? should return false for a user with nil digest" do assert_not @user.authenticated?('') end
この違うブラウザで複数の使用した時のテストは非常に再現することが難しいので
原因になった部分を再現することでテスト完了とする
学習している最中に思った疑問(混乱部分)
Q ログアウトした時にクッキーに登録しているユーザーIDを消してなかったけ?
A
# 永続的セッションを破棄する 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
で消してるよ
Q 結局どこがどうなってるのかよくわからなくなったから要点とポイントだけまとめて
A
ログアウトするとDBから永続化ログインするための情報が消える
と同時にブラウザのクッキー情報も消える
ブラウザごとにクッキーもセッションも違うので片方がクッキーが消えたからと言ってもう片方が消える訳ではない
DBから永続化ログインするための情報が消えているのでクッキーのみでログインしようとするとエラーになる
エラーになる部分はDBから永続化するための情報と記憶トークン(クッキー)を比べ一致するかをチェックするところクッキーはあるけどDBから永続化ログインするための情報がないのでエラー出力してしまう
DBから永続化するための情報がないときは即座にreturn falseして認証失敗にしてその後の処理は何もしない
自分の言葉でまとめた方が理解が深まった気がする