当方、初学者であるため、内容に不備がある可能性があります。 不備があった場合はこっそり教えてくれると幸いです。
deviseでGoogle認証を行う際に、セッションを維持したいということでRememberableモジュールを導入して使用するとエラーが返ってきたので、解決方法を調べてみました。
解決方法は結論は簡潔なのですが、deviseのメソッドの処理を見ることでなぜそのようなエラーが出るのかを理解することが出来ます。
発生した事象
デプロイする度にGoogleログインのセッションが破棄されてしまうという状態が発生していたため、remember_me(@user)
でセッションを記憶しておくようにしようと変更を加えました。
しかし、その変更を加えるとGoogle認証に失敗するようになってしまいました。
結論(一旦)
結論としては、「remember_token
カラムをusers
テーブルに追加する」もしくは「ユーザー情報を保存する際にパスワードの値をfriendly_token
でランダムなパスワードを設定する」で解決出来ます。
ランダムであれパスワードを設定したくなかったので、今回は前者の「remember_token
カラムをusers
テーブルに追加する」を行っています。
deviseの公式ドキュメントに以下のように書かれています。
remember_me(@user)
You should include module Devise::Controllers::Rememberable on your controller to use it and 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.
include Devise::Controllers::Rememberable
でモジュールを導入することは前提として、「パスワードが常に設定されている」もしくは「remember_token
カラムが存在している」もしくは「独自のロジックでrememberable_value
メソッドがモデルに存在している」ことのいずれかを満たしていること。
と書かれていますが、私はいずれも満たしていないことからGoogle認証に失敗していました。(Googleログインのみの場合、ユーザーテーブルのパスワードはnilとなっているからです。)
remember_token
カラムを追加する前に、なぜこのようなことが必要なのかが分からなかったので、Rememberable
が持つメソッドの挙動を確認することにしました。
Rememberableの全体の処理の流れ
まず前提として、rememberableの大まかな処理の流れは以下のようになっています。
- Cookieの保存(ログイン時)
- Cookieの検証(次回ログイン試行時)
- Cookieの破棄(サインアウト時)
Cookieの検証
Cookieの検証について注目としてみると、検証の途中の内部処理は以下のDevise.secure_compare(rememberable_value, token)
にて、引数のtoken
とrememberable_value
を比べて**「このクッキーのトークンはまだ期限内で、ユーザーの最新の remember_token と一致してるかどうか」**の判定を行っています。
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
次に、rememberable_value
を確認してみると、以下のようになっています。
ここでは、「remember_token
カラムがあればその値を返し、authenticatable_salt
があればその値を返し、どちらも無ければ例外処理を発生させる」といった処理になっていることが確認出来ます。
authenticatable_salt
はパスワードから生成されるものですので、パスワードが存在していなければいけません。
よって、最初の問題に戻ると、remember_token
カラムも存在していけなければ、パスワードもセットされていないことが理由でCookieの検証が出来ず、例外処理が発生していたということになります。
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
# パスワードがnilだと生成されない
def authenticatable_salt
encrypted_password[0,29] if encrypted_password
end
終わりに
以上より、なぜdeviseのRememberableモジュールを用いる際に「remember_token
カラムをusers
テーブルに追加する」もしくは「ユーザー情報を保存する際にパスワードの値をfriendly_token
でランダムなパスワードを設定する」のいずれかを満たしている必要があるかどうかの理由が分かりました。
以上です。