Railsチュートリアル9章 クッキー(remember me)
クッキー
第9章で学んだクッキーについてまとめる。
テストコードは省略する。
まずはmigration追加
$ rails generate migration add_remember_digest_to_users remember_digest:string
class AddRememberDigestToUsers < ActiveRecord::Migration[6.0] def change add_column :users, :remember_digest, :string end end
$ rails db:migrate
app/models/user.rb
class User < ApplicationRecord before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true has_secure_password validates :password, presence: true, length: { minimum: 6 } # 渡された文字列のハッシュ値を返す def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end # ランダムなトークンを返す def User.new_token SecureRandom.urlsafe_base64 end end
# これでランダムな文字列の組み合わせとしてトークンを作っている
# 永続セッションのためにユーザーをデータベースに記憶する def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end
# 作成したremember_tokenをdigest化でハッシュにしてそれをremember_digestとして保存している。
# 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end
# ハッシュ化したremember_digest(dbのカラムに保存されてる)と引数として渡されたremember_tokenが合致するか確かめる。
BCryptを使うことでハッシュ後とハッシュ前でも比較できる。
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController def new end 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 def destroy log_out redirect_to root_url end end
app/helpers/sessions_helper.rb
module SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end # ユーザーのセッションを永続的にする def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token end
#ここでクッキーに情報を入れている。 # 現在ログインしているユーザーを返す(いる場合) def current_user if session[:user_id] @current_user ||= User.find_by(id: session[:user_id]) end end # ユーザーがログインしていればtrue、その他ならfalseを返す def logged_in? !current_user.nil? end # 現在のユーザーをログアウトする def log_out session.delete(:user_id) @current_user = nil end end
current_user変更
module SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end # ユーザーのセッションを永続的にする def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token end # 記憶トークン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
user_id = session[:user_id]はイコールではなく、代入。user_idはただの変数。
だから最初のifはsession[:user_id]があればでok,あったらuser_idに代入する。
なかったらelsif文にいく。
ここでauthencticated?を使ってクッキーにあるremember_tokenとdbのカラムに保存したrember_digestを比較してあげてる。
# ユーザーがログインしていればtrue、その他ならfalseを返す def logged_in? !current_user.nil? end # 現在のユーザーをログアウトする def log_out session.delete(:user_id) @current_user = nil end end
ユーザーを忘れる
app/models/user.rb
class User < ApplicationRecord attr_accessor :remember_token before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true has_secure_password validates :password, presence: true, length: { minimum: 6 } # 渡された文字列のハッシュ値を返す def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end # ランダムなトークンを返す 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 # 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end # ユーザーのログイン情報を破棄する def forget update_attribute(:remember_digest, nil) end end
app/helpers/sessions_helper.rb
module SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end . . . # 永続的セッションを破棄する 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 end
app/controllers/sessions_controller.rbclass SessionsController < ApplicationController . . . def destroy log_out if logged_in? redirect_to root_url end end
app/models/user.rb
これはテストのせいで追加
class User < ApplicationRecord . . . # 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end # ユーザーのログイン情報を破棄する def forget update_attribute(:remember_digest, nil) end end
[remember me]チェックボックス
app/views/sessions/new.html.erb
<% provide(:title, "Log in") %> <h1>Log in</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(url: login_path, scope: :session, local: true) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :remember_me, class: "checkbox inline" do %> <%= f.check_box :remember_me %> <span>Remember me on this computer</span> <% end %> <%= f.submit "Log in", class: "btn btn-primary" %> <% end %> <p>New user? <%= link_to "Sign up now!", signup_path %></p> </div> </div>
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController def new end 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)
チェックボックスにチェックを入れると1でremember,チェックを入れないと0でfoeget redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy log_out if logged_in? redirect_to root_url end end