Railsチュートリアル 9章を終えました。
8章ではユーザログイン画面を作成していきます。
今回はRememberMe実装編です
cookie
前回の実装でも一応はログインができますが、ブラウザを閉じるたびにセッションが消去され、勝手にログアウトされます。
一時的に利用するだけですとこれでもいいですが、ブラウザが閉じられてもログインしている状態を保持もできるようにします。
そのためにcookiesを使用します
ログイン時にRemember me チェックをチェックすることでログイン状態を保持するように実装します
ただしcookiesは一時セッションとは違って盗み出される可能性があります。
これをセッションハイジャックというそうです。
それを防ぐためことも考慮し永続的セッション(ユーザが明示的にログアウトするまでに持たせるセッション)を作成するためには
1. ブラウザのcookiesには暗号化したユーザーIDと記憶トークン(ランダムな文字列)を保存する
2. サーバには記憶トークン(ランダムな文字列)を暗号化したもの(記憶ダイジェスト)を持たせる(DBにも)
3. サーバにはcookiesを受け取ったらcookiesに含まれるユーザーIDとトークンが、DBのユーザーIDとトークンに一致することを確認する
を行う感じです
1.ブラウザのcookiesには暗号化したユーザーIDと記憶トークン(ランダムな文字列)を保存する
cookie作成
cookie(永続セッション)を作成するために、ユーザーの暗号化済みIDと記憶トークンをブラウザ(cookie)に保存させます
app/helper/sessions_helper.rb
# ユーザーを永続的セッションに記憶する def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token end
2.サーバには記憶トークン(ランダムな文字列)を暗号化したもの(記憶ダイジェスト)を持たせる(DBにも)
記憶ダイジェスト作成
データベースに記憶ダイジェストを保存するために、usersテーブルに記憶ダイジェスト保持用のremember_digestカラムを追加します
$ rails generate migration add_remember_digest_to_users remember_digest:string
ランダムな文字列を生成
記憶トークンとして使う「ランダムな文字列」を生成します
app/model/user.rb
# ランダムなトークンを返す def User.new_token SecureRandom.urlsafe_base64 end
SecureRandom.urlsafe_base64は基本的には長くてランダムな文字列な文字列を生成する目的
具体的にはA–Z、a–z、0–9、“-”、“_”のいずれかの文字 (64種類) からなる長さ22のランダムな文字列を返します (そのためbase64と呼ばれています)
記憶ダイジェスト保存
次に永続的なセッションをデータベースに記憶します。
ただし記憶トークン(remember_digest)カラムは作成していませんので、インスタンス変数(remember_token)を設定してそこに記憶します
app/model/user.rb
attr_accessor :remember_token # 永続的セッションで使用するユーザーをデータベースに記憶する def remember self.remember_token = User.new_token # digestテーブルに登録する update_attribute(:remember_digest, User.digest(remember_token)) end # 与えられた文字列のハッシュ値を返す def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end
BCrypt::Password.create(string, cost: cost)
簡単に、パスワードを作成します。
costはコストパラメータで、ハッシュを算出するための計算コストを指定します。
高ければ高いほど計算度が高くなります。
3.サーバにはcookiesを受け取ったらcookiesに含まれるユーザーIDとトークンが、DBのユーザーIDとトークンに一致することを確認する
トークン一致確認
最後にcookiesとDBが一致することを確認します。
DBのremember_digest
とcookiesのremember_token
が一致することを確認します
app/model/user.rb
# 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end
cookie削除
次にログアウト時のセッション削除を行います
セッション削除はセッション記憶と逆のことをします。
記憶ダイジェストをnilにし、cookieを削除すればいい感じです
app/model/user.rb
# ユーザーのログイン情報を破棄する def forget update_attribute(:remember_digest, nil) end
app/helpers/sessions_helper.rb
# 永続的セッションを破棄する def forget(user) user.forget cookies.delete(:user_id) cookies.delete(:remember_token) end
あとはlog_outヘルパーにforgetを読み込ませます
# 現在のユーザーをログアウトする def log_out forget(current_user) session.delete(:user_id) @current_user = nil end
コントローラからの呼び出し
ここまできたら
app/controllers/sessions_controller.rb
def create @user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password]) log_in @user params[:session][:remember_me] == '1' ? remember(@user) : forget(@user) redirect_to @user else #ログイン失敗 end end
とチェックボックスのチェックによってsession_helper.rb
のrememberメソッド
とsession_helper.rb
のforgetメソッドを使い分ければいい感じです
'remember'メソッドがモデルuser.rb
にもありますがこちらではないです
現在設定中のユーザー
なお記憶トークンがcookieにある場合(ブラウザ開閉などをおこなった場合)、sessionには保存されていないので current_userはDBから取って来る必要があります
# 記憶トークン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
終わりに
このセッションとcookieの使い分けは一回見ただけではよくわからなかったです
2回ほど見た後に、参照記事を見てようやく概要がわかった感じです
ここは後々に重要になりそうですね
参照
記事作りに参考になりました。ありがとうございます www.masalog.site