Railsチュートリアル9章まとめ。ログイン画面実装(RememberMe実装編)

Railsチュートリアル 9章を終えました。
8章ではユーザログイン画面を作成していきます。
今回はRememberMe実装編です

railstutorial.jp

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_digestcookiesの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.rbrememberメソッド
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