##はじめに
railsチュートリアルで理解しにくいところや、詰まったところを書いていく記事になります。
なので、手順を示す記事とはなっていません。
##Remember me 機能
[remember me] 機能
を利用すると、ユーザーが明示的にログアウトを実行しない限り、ログイン状態を維持することができるようになる。
この機能を利用するには、記憶トークン (remember token)を生成し、cookiesメソッドによる永続的cookiesの作成や、安全性の高い記憶ダイジェスト (remember digest) によるトークン認証にこの記憶トークンを活用することが必要となる。
(トークンとは、パスワードと似たようなものであるが、違いとしてはトークンの場合はコンピュータが作成した情報である)
永続的セッションを作成するにあたって、以下のようなことを留意点とする。
・記憶トークンにはランダムな文字列を生成して用いる。
・ブラウザのcookiesにトークンを保存するときには、有効期限を設定する。
・トークンはハッシュ値に変換してからデータベースに保存する。
・ブラウザのcookiesに保存するユーザーIDは暗号化しておく。
・永続ユーザーIDを含むcookiesを受け取ったら、そのIDでデータベースを検索し、記憶トークンのcookiesがデータベース内のハッシュ値と一致することを確認する。
###rememberダイジェストを追加
最初の手順として、マイグレーションファイルを生成し、remember_digest属性(string)
をUserモデルに追加する。
また、remember_digestはユーザーが直接生み出すことはないので、インデックスを追加する必要がない。
###記憶トークンの作成
記憶トークンには基本的に長くてランダムな文字列が求められるので今回はSecureRandomモジュールにある urlsafe_base64メソッド
を使用する。
このメソッドは、A–Z、a–z、0–9、"-"、"_"のいずれかの文字 (64種類) からなる長さ22のランダムな文字列を返す。
例:
$ rails console
>> SecureRandom.urlsafe_base64
=> "q5lt38hQDc_959PVoo6b7A"
ユーザーを記憶するには、記憶トークンを作成して、そのトークンをダイジェストに変換したものをデータベースに保存すればいい。
ここで、トークン生成用のメソッドを追加する。
# ランダムなトークンを返す
def User.new_token
SecureRandom.urlsafe_base64
end
次に有効なトークンとそれに関連するダイジェストを作成する。
具体的には、最初に User.new_token
で記憶トークンを作成し、続いて User.digest
を適用した結果で記憶ダイジェストを更新する。
attr_accessor :remember_token
.
.
.
# 永続セッションのためにユーザーをデータベースに記憶する
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
rememberメソッド
の1行目の代入の箇所では、self
というキーワードを使わないと、Rubyによってremember_tokenという名前のローカル変数が作成されてしまう。
selfキーワードを与えると、この代入によってユーザーのremember_token属性が期待どおりに設定される。
rememberメソッドの2行目では、update_attributeメソッド
を使って記憶ダイジェストを更新している。
###ログイン状態の保持
user.rememberメソッド
が動作するようになったので、ユーザーの暗号化済みIDと記憶トークンをブラウザの永続cookiesに保存して、永続セッションを作成する準備ができた。
これを実行するには cookiesメソッド
を使う。
以下に手順を示す。
.
.
.
# 渡されたトークンがダイジェストと一致したらtrueを返す
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
rememberヘルパーメソッド
を追加して、log_in
と連携させる。
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ヘルパーの動作は、rememberメソッド定義のuser.rememberを呼び出すまで遅延され、そこで記憶トークンを生成してトークンのダイジェストをデータベースに保存される。
続いて上と同様に、cookiesメソッドでユーザーIDと記憶トークンの永続cookiesを作成する。
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ヘルパー
を追加修正する必要がある。
記憶トークンcookieに対応するユーザーを返す↓
# 記憶トークン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
###ユーザーを忘れる
ユーザーがログアウトできるように、ユーザーを記憶するためのメソッドと同様の方法で、ユーザーを忘れるためのメソッドを定義する。
具体的には、記憶ダイジェストをnilで更新するメソッドを定義することとなる。
forgetヘルパーメソッド
を追加して、log_outヘルパーメソッド
から呼び出す。
.
.
.
# ユーザーのログイン情報を破棄する
def forget
update_attribute(:remember_digest, nil)
end
end
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
###[Remember me] チェックボックス
ログインフォームに チェックボックス
を追加する。
チェックボックスは、他のラベル、テキストフィールド、パスワードフィールド、送信ボタンと同様にヘルパーメソッドで作成できる。
<% provide(:title, "Log in") %>
<h1>Log in</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(:session, url: login_path) 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>
2つのCSSクラス checkbox
inline
を使い、Bootstrapではこれらをチェックボックスとテキスト「Remember me on this computer」として同じ行に配置することができる。
最後に、チェックボックスがオンのときにユーザーを記憶し、オフのときには記憶しないようにする。
ログインフォームから送信されたparamsハッシュには既にチェックボックスの値が含まれていて、オンの時は1、オフの時は0になるので、三項演算子を使うことで、コンパクトに処理を書くことができる。
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
ちなみに三項演算子を使わない場合は追加部分は以下のようなコードとなる。
if params[:session][:remember_me] == '1'
remember(user)
else
forget(user)
end
##おわりに
普段、当たり前のようにお世話になっている機能がここまで難しとは、、という感じです、、、
徐々に理解していこうと思います!
次↓
https://qiita.com/jonnyjonnyj1397/items/dda1a3b33bf9a1c0f982