はじめに
Ruby on Rails チュートリアルで学んだCookieを利用した自動ログイン
備忘録
ブラウザを閉じた後でもログインを有効にするRemember me
を実装する
Ruby on Railsを学んでいる方・学ぶ予定の方
前提の記事を読んだ後、こちらを読んでいただければ教材を進める助けになると思います。
前提
自動ログインのない基本的なログイン機構を実装していることが前提です
上記記事でログイン機能の実装を把握した後にこちらを読んでください。
どちらもRuby on Railsチュートリアルのコードを利用しています。
素敵な記事をお借りします。
remember_digest
属性をUserモデルに追加
# 語尾の_to_usersはmigration対象がuses_tableだと明示
$ rails generate migration add_remember_digest_to_users remember_digest:string
$ rails db:migrate
メソッドを追加する
User
モデル
attr_accessor :remember_token # 仮想カラムを用意する
# 渡された文字列のハッシュ値を返す
def self.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# ランダムなトークンを返す
def self.new_token
SecureRandom.urlsafe_base64 # 標準のmodele
end
# 永続セッションのためにユーザーをデータベースに記憶する
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# 渡されたトークンがダイジェストと一致したら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
helperを定義する
app/helpers/sessions_helper.rb
# ユーザーのセッションを永続的にする
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
ログインと連動させる
app/controllers/sessions_controller.rb
def create
.
.
remember user
.
end
current_user
を更新する
ログインと連携させた時点でcookiesには有効なidとtokenが保存されたが、保存したcookiesを使用する記述をしていない
有効なcookiesがあれば自動ログインできるようにする
app/helpers/sessions_helper.rb
# 記憶トークン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
ユーザーを忘れる
forget(user)
ヘルパーメソッドを作成する
log_out
からfoget(user)
を呼び出す
app/helpers/sessions_helper.rb
# 永続的セッションを破棄する
def forget(user)
user.forget # forgetはmodelのインスタンスメソッド
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# 現在のユーザーをログアウトする
def log_out
forget(current_user) # これを追記
session.delete(:user_id)
@current_user = nil
end
小さなバグに対応する
2つのタブで2回ログアウトを実行した際forget(current_user)
が失敗する
2種類のブラウザで利用した際、片方だけログアウトして、もう片方で再度訪れると、remember_digest
がnil
なのにcookiesで自動ログインしようとして、Bcrypt
のtokenとremember_digestの比較で例外が発生する
ログインしている時だけログアウト処理を実行する
app/controllers/sessions_controller.rb
def destroy
log_out if logged_in? # ここを編集
redirect_to root_url
end
記憶ダイジェストがnil
の場合にfalse
を返す
app/models/user.rb
# 渡されたトークンがダイジェストと一致したらtrueを返す
def authenticated?(remember_token)
return false if remember_digest.nil? # これを追記した
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
チェックボックで永続的ログインを選択する
ログインのフォームに下記を追記する
# labelの内側に配置する配置する
<%= f.label :remember_me, class: "checkbox inline" do %>
<%= f.check_box :remember_me %>
<span>Remember me on this computer</span>
<% end %>
チェックボックスの仕様
ONの時 '1'
になり、OFの時 '0'
になる
createアクションを編集する
def create
.
.
# チェックが入っている時のみtokenを記憶する 三項演算子
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
.
end
終わりに
Ruby on RailsチュートリアルはRailsの使い方というより、Webアプリケーションの作り方を体系的に学べます。
ログイン機構の仕組みは他の言語やフレームワークを使った場合でも同じなので、書き方ではなくどういう仕組みなのかを理解したいですね。