- 更新日: 2014年1月1日
- Devise
Devise3.2.2 のデフォルト設定では、Rememberable の remember_token のカラムがないのでソースを解読してみた
Rails4 で使う認証エンジン Devise には、一定期間ユーザーのログイン状態を持続させる Rememberable モジュールがあるのですけど、デフォルトのマイグレーションではデータベースのテーブルに remember_token カラムが生成されないのがちょっと気になりまして、ソースコードを読んで解読してみました。
— 環境 —
Rails 4.0.1
Devise 3.2.2
Devise のデフォルトでのマイグレーション
Devise のデフォルトは、:database_authenticatable, :recoverable, :registerable, :rememberable, :trackable, :validatable のモジュールが有効で、以下がモデル名を user としての初期導入した時の、DBテーブル作成用のマイグレーションとなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class DeviseCreateUsers < ActiveRecord::Migration def change create_table(:users) do |t| ## Database authenticatable t.string :email, :null => false, :default => "" t.string :encrypted_password, :null => false, :default => "" ## Recoverable t.string :reset_password_token t.datetime :reset_password_sent_at ## Rememberable t.datetime :remember_created_at # ・・・ end # ・・・ end |
Rememberable モジュールでは、remember_created_at というカラムを作成するだけとなっています。remember_token というカラムはデフォルトでは存在しません。
Rememberable モジュールのソース解読
ここからソースコード解読。まずは Rememberable モジュールの中身を読んでみる。
liv/devise/models/rememberable.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
module Devise module Models # ・・・ module Rememberable extend ActiveSupport::Concern #・・・ def remember_me!(extend_period=false) self.remember_token = self.class.remember_token if generate_remember_token? self.remember_created_at = Time.now.utc if generate_remember_timestamp?(extend_period) save(:validate => false) if self.changed? end # ・・・ |
この remember_me! でログイン中のユーザーを記憶して、一定期間セッションを持続させます。remember_me! メソッドの1行目は、remember_token フィールドがなければ実行されないはずということで、generate_remember_token? メソッドの中身を見てみる。
1 2 3 4 5 |
protected def generate_remember_token? #:nodoc: respond_to?(:remember_token) && remember_expired? end |
予想通り、respond_to? が false を返すので、先の remember_me! の1行目は実行されず、2行目 remember_created_at がレコードに保存されることになります。ただし、これだと remember_token の役割として、ユーザーのクッキーに保存したトークンと照合させるために、DB内のどのフィールドを使うのかまだ不明です。
コメント内のヒントを読む
冒頭コメントの最後の部分に、以下の記述がありヒントになります。
1 2 3 4 5 |
# # 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) |
クッキーに情報を保存する場合は、User.serialize_into_cookie クラスメソッドを使い、クッキー由来に基づくユーザーを探す場合、User.serialize_from_cookie メソッドを使うとある。その部分のソースを読んでみると以下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
module ClassMethods # Create the cookie key using the record id and remember_token def serialize_into_cookie(record) [record.to_key, record.rememberable_value] end # Recreate the user based on the stored cookie def serialize_from_cookie(id, remember_token) record = to_adapter.get(id) record if record && !record.remember_expired? && Devise.secure_compare(record.rememberable_value, remember_token) end #・・・ |
これで、トークンとして使われる値が、record.rememberable_value という値であることが分かりました。
encrypted_password フィールドの先頭30文字が remember_token の代用
続いて、record.rememberable_value の中身を探します。
1 2 3 4 5 6 7 8 9 10 11 12 |
def rememberable_value if respond_to?(:remember_token) remember_token elsif respond_to?(:authenticatable_salt) && (salt = authenticatable_salt) salt else raise "authenticable_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 |
この else 節の raise のメッセージにもヒントがあります。訳しますと…
“Rememberable モジュールを使うためには、パスワードが必ず設定されているか、remember_token カラムがモデルに存在するか、独自の rememberable_value メソッドをモデルに実装する必要がある。”
ということで、remember_token フィールドがない場合は、必ずパスワードが設定されていなければなりません。これで、remember_token の代わりにパスワードが代用されるのだろうと、予測がつきました。
remember_token フィールドがない場合は、この elseif 文で rememberable_value の値(salt)が返されるはずなので、salt に値を設定する authenticatable_salt を調査します。この authenticatable_salt インスタンスメソッドは、残念ながらこのファイル(liv/devise/models/rememberable.rb)には定義されておらず、ソースを追いますと lib/devise/models/database_authenticatable.rb で定義されています。
lib/devise/models/database_authenticatable.rb
1 2 3 4 |
# A reliable way to expose the salt regardless of the implementation. def authenticatable_salt encrypted_password[0,29] if encrypted_password end |
これにより、encrypted_password フィールドの値(暗号化されたパスワード)の先頭30文字が、remember_token の代用として流用されることが分かりました。この値を使って、serialize_into_cookie(record) でクッキーを保存してユーザーのログイン状態を一定期間持続させ、また serialize_from_cookie(id, remember_token) でクッキーからログイン持続中のユーザー情報を読み出すことが分かりました。remember_token フィールドが存在しなくても、Rememberable が動作することを理解できて一安心です。
Rememberable モジュールを使う場合のユニットテスト
user_spec.rb に以下のように簡単にテストを書きました。
1 2 3 4 5 |
# devise uses encrypted_password[0.29] as a remember token describe "remember token" do before { @user.save } its(:encrypted_password) { should_not be_blank } end |
Rememberable モジュールを使う場合で、remember_token フィールドを作成しない場合は、必ずパスワード(encrypted_password)のフィールドの値が保存される必要があります。
あけましておめでとうございます。今年もどうぞよろしくお願いいたします。
- Devise の関連記事
- RailsのDevise認証機能での実装チェックリストまとめ
- Deviseで送信されるメールのfrom(送信者メールアドレス)を変更
- Facebook の OAuth 認証で OAuthException(191)エラー
- Rails Devise でパスワードリセットなどのメールテンプレート(Mailer ビュー)をカスタマイズ
- Rails + Devise 環境でのフレンドリーフォワーディング機能を修正
- Deviseでユーザー登録完了時にウェルカムメールを送信する
- Rails Devise でユーザーがプロフィール情報を更新後に元のページにリダイレクトさせる
- Devise でユーザーがパスワードなしでアカウント情報を変更するのを許可
- Rails Deviseの日本語化辞書ファイル(devise.ja.yml)
- Rails + Devise で admin ユーザー(管理者)を削除できないようにする
- 2件のコメント
パスワードなしの時に remeberable が効かなくて困っていたので、非常に助かりました。
ありがとうございますm(__)m
匿名さん
お役に立てて幸いです。こちらこそ記事をお読み頂きありがとうございました。