【Devise】Rememberable
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
deviseのモデル設定に関する記事も連続投稿3投目!
今回はRememberableについてざっくり理解していきたいと思います!
しかし、連続投稿としては今回で最後にします。理由としてはポートフォリオ制作を進めるために他の記事も書きたいためです。なので、別の機会にdeviseについてまた書きたいと思います!(特に、外部認証であるOmniauthableについてはポートフォリオ制作で使っているため、必ず記事にしたいです!)
概要
Rememberableもマイグレーションファイルやモデルにデフォルトで追加されるモデルの設定になります。
[モデル]
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
[マイグレーションファイル]
## Rememberable t.datetime :remember_created_at
rememberableで使用するカラムは一つのみです。remember_created_at
とあるように
何かの日付を記録するためのカラムを設定しています。何の日付を記憶するのかモジュールを見て推察しましょう。
今回もまたまた長いモジュールとなっています。
# frozen_string_literal: true require 'devise/strategies/rememberable' require 'devise/hooks/rememberable' require 'devise/hooks/forgetable' module Devise module Models # Rememberable manages generating and clearing token for remembering the user # from a saved cookie. Rememberable also has utility methods for dealing # with serializing the user into the cookie and back from the cookie, trying # to lookup the record based on the saved information. # You probably wouldn't use rememberable methods directly, they are used # mostly internally for handling the remember token. # # == Options # # Rememberable adds the following options in devise_for: # # * +remember_for+: the time you want the user will be remembered without # asking for credentials. After this time the user will be blocked and # will have to enter their credentials again. This configuration is also # used to calculate the expires time for the cookie created to remember # the user. By default remember_for is 2.weeks. # # * +extend_remember_period+: if true, extends the user's remember period # when remembered via cookie. False by default. # # * +rememberable_options+: configuration options passed to the created cookie. # # == Examples # # User.find(1).remember_me! # regenerating the token # User.find(1).forget_me! # clearing the token # # # generating info to put into cookies # User.serialize_into_cookie(user) # # # lookup the user based on the incoming cookie information # User.serialize_from_cookie(cookie_string) module Rememberable extend ActiveSupport::Concern attr_accessor :remember_me def self.required_fields(klass) [:remember_created_at] end def remember_me! self.remember_token ||= self.class.remember_token if respond_to?(:remember_token) self.remember_created_at ||= Time.now.utc save(validate: false) if self.changed? end # If the record is persisted, remove the remember token (but only if # it exists), and save the record without validations. def forget_me! return unless persisted? self.remember_token = nil if respond_to?(:remember_token) self.remember_created_at = nil if self.class.expire_all_remember_me_on_sign_out save(validate: false) end def remember_expires_at self.class.remember_for.from_now end def extend_remember_period self.class.extend_remember_period end def rememberable_value if respond_to?(:remember_token) remember_token elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt.presence) salt else raise "authenticatable_salt returned nil for the #{self.class.name} model. " \ "In order to use rememberable, you must ensure a password is always set " \ "or have a remember_token column in your model or implement your own " \ "rememberable_value in the model with custom logic." end end def rememberable_options self.class.rememberable_options end # A callback initiated after successfully being remembered. This can be # used to insert your own logic that is only run after the user is # remembered. # # Example: # # def after_remembered # self.update_attribute(:invite_code, nil) # end # def after_remembered end def remember_me?(token, generated_at) # TODO: Normalize the JSON type coercion along with the Timeoutable hook # in a single place https://github.com/heartcombo/devise/blob/ffe9d6d406e79108cf32a2c6a1d0b3828849c40b/lib/devise/hooks/timeoutable.rb#L14-L18 if generated_at.is_a?(String) generated_at = time_from_json(generated_at) end # The token is only valid if: # 1. we have a date # 2. the current time does not pass the expiry period # 3. the record has a remember_created_at date # 4. the token date is bigger than the remember_created_at # 5. the token matches generated_at.is_a?(Time) && (self.class.remember_for.ago < generated_at) && (generated_at > (remember_created_at || Time.now).utc) && Devise.secure_compare(rememberable_value, token) end private def time_from_json(value) if value =~ /\A\d+\.\d+\Z/ Time.at(value.to_f) else Time.parse(value) rescue nil end end module ClassMethods # Create the cookie key using the record id and remember_token def serialize_into_cookie(record) [record.to_key, record.rememberable_value, Time.now.utc.to_f.to_s] end # Recreate the user based on the stored cookie def serialize_from_cookie(*args) id, token, generated_at = *args record = to_adapter.get(id) record if record && record.remember_me?(token, generated_at) end # Generate a token checking if one does not already exist in the database. def remember_token #:nodoc: loop do token = Devise.friendly_token break token unless to_adapter.find_first({ remember_token: token }) end end Devise::Models.config(self, :remember_for, :extend_remember_period, :rememberable_options, :expire_all_remember_me_on_sign_out) end end end end
まず前回までの記事同様に英語の説明部分を見てみます。
Rememberable manages generating and clearing token for remembering the user from a saved cookie. Rememberable also has utility methods for dealing with serializing the user into the cookie and back from the cookie, trying to lookup the record based on the saved information. You probably wouldn't use rememberable methods directly, they are used mostly internally for handling the remember token.
訳:
Rememberableは、保存されたcookie
からユーザを記憶するためのトークンの生成とクリアを管理する機能です。ユーザーをcookie
にシリアライズしたり、cookie
から戻したり、保存された情報に基づいてレコードを検索したりするためのメソッドもあります。
これらのメソッドは、主に内部で記憶トークンの処理に使用されます。
cookie
を使用してユーザーを記憶するためのトークンを生成するとあるようにセッション管理に該当するのかなと思います。つまり、Rememberableはログインしたユーザーの情報を管理している機能ということです。しかし、具体的な機能が少し予想しづらいですね。
モジュールのメソッドを見てその機能を予想していきましょう。
3つのオプション
昨日のRecoverableと同じようにRememberableにも同じように3つのオプションがあるようです。
# Rememberable adds the following options in devise_for: # # * +remember_for+: the time you want the user will be remembered without # asking for credentials. After this time the user will be blocked and # will have to enter their credentials again. This configuration is also # used to calculate the expires time for the cookie created to remember # the user. By default remember_for is 2.weeks. # # * +extend_remember_period+: if true, extends the user's remember period # when remembered via cookie. False by default. # # * +rememberable_options+: configuration options passed to the created cookie.
英語の説明をざっくり訳すと以下のような感じ
remember_for
(ユーザーの認証情報の記憶時間の長さ)extend_remember_period
(cookie
から記憶されているユーザー情報の記憶時間の延長するかどうか)rememberable_options
(cookie
に渡されるオプションの設定)
ここで2つの疑問があるかと思います。
remember_for
とextend_remember_period
は依存関係ではないか。rememberable_options
でできるオプションの設定とは何か。
まず1についてですが、これは2つの設定は依存関係にあるのかなと思います。remember_for
で設定したことを反映させるためにはextend_remember_period
が必要になると思うので、どちらか片方が設定されるということはないと思います。extend_remember_period
をtrue
に変更することで記憶期間の延長を許可し、remember_for
でその記憶期間を設定するという感じですね。
※ cookie
を使用した時のみextend_remember_period
は適応されるということなので、cookie
を使わない場合は適応されないのだと思います。ここの違いはあまり理解できませんでした、、
2についてあまり説明している記事が見当たらなかったのですが、下記記事を見つけました。個人の記事なので信頼性はわかりません。
rememberable_options
は記事の中で以下のように定義されています。
config.rememberable_options = {:secure => true, :same_site => :none}
cookie
の有効を同じサイトにするか、secure
なものにするかなどの設定のようです。しかし、これを設定しているGithubの公開リポジトリはあまり見受けられなかったので、そこまで重要な設定ではないのでしょうか。自分はそう感じました。
これもまたdevise.rbで設定するようです。
config.remember_for = 1.week config.extend_remember_period = false config.rememberable_options = {}
3つのオプションの設定自体はそこまで重要ではないようですが、ログイン情報を保存する長さの設定など、ログイン情報の記憶に関する機能ということは間違いなさそうです。
モジュールのメソッド
今回もモジュールが長いので使用例の中からメソッドを見ていきましょう。
# == Examples # # User.find(1).remember_me! # regenerating the token # User.find(1).forget_me! # clearing the token
remember_me!
まずはremember_me!
メソッドです。
remember_me!
def remember_me! self.remember_token ||= self.class.remember_token if respond_to?(:remember_token) self.remember_created_at ||= Time.now.utc save(validate: false) if self.changed? end
regenerating the token
とあるように記憶トークンを再度生成しているのだと思われます。
||=
を使っているのでremember_token
がnil
の時self.class.remember_token
のメソッドで返却される値をremember_token
に格納しています。
self.class
とあるように、モジュールの中にあるモジュールのメソッドを使っているようです。ClassMethodsモジュールがRememberableの中に定義されていました。
module Rememberable ・ ・ module ClassMethods # Generate a token checking if one does not already exist in the database. def remember_token #:nodoc: loop do token = Devise.friendly_token break token unless to_adapter.find_first({ remember_token: token }) end end end end
Devise.friendly_token
でトークンを生成しているようです。unless to_adapter.find_first({ remember_token: token })
でトークンの値が重複しないように制御している感じですね。
そしてremember_created_at
カラムを更新しています。
self.remember_created_at ||= Time.now.utc
forget_me!
forget_me!
メソッドについてはremember_me!
の逆の挙動をします。
# If the record is persisted, remove the remember token (but only if # it exists), and save the record without validations. def forget_me! return unless persisted? self.remember_token = nil if respond_to?(:remember_token) self.remember_created_at = nil if self.class.expire_all_remember_me_on_sign_out save(validate: false) end
remember_token
とremember_created_at
にnil
を格納していますね。ログイン情報の記憶トークンのリセットをしています。
ある程度これで把握できたかなと思います!