記憶トークンの作成
Ruby標準ライブラリのSecureRandomモジュールにあるurlsafe_base64メソッド(A–Z、a–z、0–9、"-"、"_"のいずれかの文字 (64種類) からなる長さ22のランダムな文字列を返す)を使用して、記憶トークンを作成する
具体的には、
class User < ApplicationRecord
attr_accessor :remember_token
# (省略)
# ランダムなトークンを返す
def User.new_token
SecureRandom.urlsafe_base64
end
# 永続セッションのためにユーザーをデータベースに記憶する
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
end
としておく。
クラスメソッドのnew_tokenでは記憶トークンを作成。rememberメソッドでは、現在のユーザーのremember_token属性をnew_tokenメソッドを用いて作成する。その後、update_attributeの第一引数に変更を加える属性(:remember_digest)を指定し、第二引数にremember_tokenをdigestメソッドを用いて暗号化したものをセッティングして、値の更新を行っている。
つまり、rememberメソッドでは、記憶トークンを現在のユーザーと紐づけ、その記憶トークンを暗号化して、DBにremember_digest属性として保存している。
cookiesに保存
# (省略)
# ユーザーのセッションを永続的にする
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
上のrememberメソッドでは、user.rememberで先程のrememberクラスメソッドを引数で与えられたユーザーに対して実行。
cookies.permanent.signed[:user_id] = user.id
では、permanent(期限の設定)とsigned(署名付きにする)をメソッドチェーンしてcookiesに対して行う。またcookiesはsessionと同様にハッシュのように取り扱うことができる。
記憶トークンの方も同様にcookiesに保存している。
記憶トークンの認証
# 渡されたトークンがダイジェストと一致したらtrueを返す
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
上のコードで、暗号化されたremember_digestと、引数として与えられた値を暗号化したものが同じになるか比較して認証。
current_userメソッドをcookiesに対応させる
# 記憶トークン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
if (user_id = session[:user_id])の部分では、変数user_idにsession[:user_id]の値を代入した結果、session[:user_id]が存在すれば、という意味。
永続的セッションの削除
# ユーザーのログイン情報を破棄する
def forget
update_attribute(:remember_digest, nil)
end
ユーザーモデルにforgetメソッドを定義して、remember_digest属性をnilにする
# 永続的セッションを破棄する
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
forgetで永続的セッションの削除。log_outで永続的セッションの削除と、セッションの削除を行う。
チェックボックスの作成
<%= f.label :remember_me, class: "checkbox inline" do %>
<%= f.check_box :remember_me %>
<span>Remember me on this computer</span>
<% end %>
ここで与えられたデータは、params[:session][:remember_me]で取り出せる。
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
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
params[:session][:remember_me] == '1' ? remember(user) : forget(user)は三項演算子を使っている。
論理値? ? 何かをする : 別のことをする
上のコードでは、params[:session][:remember_me]の値が1なら、remember(user)を実行し、それ以外なら、forget(user)を実行するということ。
ログイン時にforget(user)を実行して大丈夫なのだろうかと思ったけれど、cookies関連の情報がnilになるだけなので、ログインはできる。