remember機能の実装

おはようございます。

本日はrails tutorial9章、remember機能の実装についてまとめたいと思います。

永続的セッションを実装するための実装は以下のように記述されました。。

  1. 記憶トークンにランダムな文字列を用いる
  2. ブラウザのcookiesにトークンを保存するときは有効期限を設定する
  3. トークンはハッシュ値にしてから保存する
  4. ブラウザのcookiesに保存するユーザーidは暗号化する
  5. 永続ユーザーIDを含むcookiesを受け取ったら、そのIDでデータベースを検索し、記憶トークンのcookiesがデータベース内のハッシュ値と一致することを確認する

少しわかりにくいですよね笑初めて読んだときはわけ分からなくなりました。

これを自分なりに解釈したところ

  1. 記憶トークンをランダムな文字列を用いて作成し、さらにそれを暗号化した記憶ダイジェストを作る
  2. ユーザーの暗号化済みIDと記憶トークンを使いcookiesを作成する
  3. ユーザーがサイトに来た際、暗号化済みのIDと記憶トークンを持ちいて認証を行う

といった流れかなと思います。

remember機能の実装

では、早速説明していこうと思います。

remember_digestの追加

まず、最初にユーザーに記憶トークンを持たせるためのremember_digestカラムをUserモデルに追加します。

rails g migraiton add_remember_digest_to_users remember_digest:string
rails db:migrate

この二つのコマンドで実装できました。

ランダムな文字列の作成

記憶トークンはランダムな文字列でなければなりません。
TutorialではRuby標準ライブラリのSecureRandomモジュールにあるurlsafe_base64メソッドを使います。

よって以下のメソッドをUserモデルに追記します。

def User.remember_token
SecureRandom.urlsafe_base64
end

これによりランダムな文字列が追加されます。

User.rememberメソッドで記憶トークンをユーザーと関連付け、記憶ダイジェストをDBに保存する

remeber_digest(記憶ダイジェスト)は追加しましたが、記憶トークンはまだ追加されていません。
そのため、まずattr_accessorメソッドにremember_token(記憶トークン)を宣言し、アクセスできるようにします。

User.rb

attr_asseccor: remember_token
・
・
・
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest,User.digest(remember_token))
end

cookiesで永続セッション作成

ユーザーの暗号化済みのIDと記憶トークンをcookiesに保存して永続セッションを作成します。

これにはcookiesメソッドを使います。
cookiesは一つの値と有効期限から出来ています。

cookies[:remember_token] = {value: remember_token, expires: 20.years.from_now.utc}

上記のコードはpermanentメソッドを使うとシンプルになります。

cookies.permanent[:remember_token] = remember_token

ユーザーIDを保存するには

cookies[:user_id] = user.id

と記述します。

ユーザーIDと記憶トークンはセットで使うのでこちらも永続的でなければなりません。
また、このままだとセキュリティ的に良しとしないのでさらに改良します。

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

signedメソッドによって暗号化できます。

記憶トークンと記憶ダイジェストを使い認証を行う

この認証にはパスワード認証の際にも使ったbcryptを用います。

User.rb

def authenticate?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end

次は実際にユーザーを記憶します。

sessions_controller

def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
remember user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end

sessions_helper

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

既存コードの修正

今までのコードは一時セッションを前提に書かれていたのでそれを少し変更します。

現時点では一時セッションしか扱ってないので正常には機能しません。
永続セッションの場合はsession[:user_id]が存在すれば一時セッションからユーザーを取り出し、それ以外の場合はcookies[:user_id]から取り出し、対応する永続セッションにログインする必要があります。

def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
else (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated(remember_token)
log_in user
@current_user = user
end
end
end

永続セッションの破棄

手順は以下の通りです。

  1. remember_digestをnilにするメソッドを作成
  2. 1のメソッドを用いて、cookiesを削除し、ログアウトする

1については記憶ダイジェストをnilで更新すればokです。

User.rb

def forget
update_attribute(:remember_digest, nil)
end

次に1のメソッドを用いてcookiesを削除し、ログアウトします。

sessions_helper

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

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です