
Rails 5.2のcredentialsを使うとDeviseからエラーが発生する問題と応急処置
※この問題はDevise 4.4.3以下で起きます。次期バージョンである4.5.0では修正されました。
発生した問題
とあるプロジェクトをRails 5.2で作っているのですが、Deviseを導入していくつかの設定を行った所、次のようなエラーが出てくるようになりました:
$ rails c
/usr/local/bundle/gems/devise-4.4.3/lib/devise/rails/routes.rb:500:in `raise_no_secret_key': Devise.secret_key was not set. Please add the following to your Devise initializer:
config.secret_key = '1e2903bbf3c5f9b45b398332c421ab572c5e272253100d2603ad038b33fb3b1203e049c4b088a6b19434fe6018a37235f7bf528c453ba957cdb62a4b7374e912'
Please ensure you restarted your application after installing Devise or setting the key.
(RuntimeError)
from /usr/local/bundle/gems/devise-4.4.3/lib/devise/rails/routes.rb:228:in `devise_for'
from /app/config/routes.rb:4:in `block in <main>'
Deviseではパスワードリセット等で使用するランダムなトークンの生成に、config/initialzers/devise.rbで設定した config.secret_key
の値を使用します。
デフォルトではこの設定は未指定の場合、Railsのsecret_key_baseが使われる為コメントアウトされています。任意の値を設定したい時にコメントアウトして使う物なので、通常は設定する必要がありません。
では、何故このようなエラーが発生したのでしょうか。

Rails 5.2で新しく導入されたcredentials
Railsでは5.2から秘匿情報の扱いが変わりました。具体的には config/secrets.yml
を廃止し、代わりに config/credentials.yml.enc
が追加されました。secret_key_baseもこのファイルに保存します。(credentials.yml.encについてはこちらのQiitaが詳しいです)
さて、このcredentials.yml.encですが、中身はファイル名の通り暗号化されており、同じく新規に追加された config/master.key
あるいは ENV["RAILS_MASTER_KEY"]
(以下、複合キー) によって実行時に複合されます。
また、当然、credentials.yml.encには秘匿情報が格納されている為、master.keyはGitによるバージョン管理はされません。
そうなると、他の開発メンバーが加わった際は複合キーを共有する必要があります。しかし、開発メンバーのロールによってはこれらを共有する事ができないケースも考えられます。
では、複合キーがない状態でRailsを走らせるとどうなるのでしょうか?実際に試してみると、普通に動かす事ができます。
credentialsはsecretsとは違い、環境ごとに値を分ける事はできません。そもそも、秘匿情報の管理自体が本番環境でのみ必要となるので、環境ごとに分ける必要が無いと言うスタンスのようです。
この為、開発環境では(複合キーがない場合は)単に Rails::Application.credentials
の中身が空になるだけで、エラーにはなりません。
さて、本番でしか使われない事がわかったcredentialsですが、それではsecret_key_baseはどうなってしまうのでしょうか?
答えは単純で、開発環境ではsecret_key_baseはプロジェクト毎に固定の値が使われるようになっています。
新しく追加された Rails::Application.secret_key_base
によって取得が可能で、Applicationクラスのクラス名のmd5値が使われます。Applicationクラス名が “MyBlog::Application” なら、 fed42568a7c9da7d8f43ec1cd7e60b57
と言った具合です。
確かに、これであれば複合の必要自体がなくなります。また、開発環境のsecret_key_baseなんて漏れてもダメージはないのでこれで問題ありませんね。
Deviseでのsecret_key_baseの取扱
話を冒頭に戻します。Deviseはデフォルトではsecret_key_baseの値を参照しますが、実装はどのようになっているのでしょうか?Devise(
執筆時点で最新の4.4.3の)のソースを見ると、 Devise::SecretKeyFinder
がその役を担っており、実装は次のようになっています:
# https://github.com/plataformatec/devise/blob/v4.4.3/lib/devise/secret_key_finder.rb#L10-L15
if @application.respond_to?(:credentials) && key_exists?(@application.credentials)
@application.credentials.secret_key_base
elsif @application.respond_to?(:secrets) && key_exists?(@application.secrets)
@application.secrets.secret_key_base
elsif @application.config.respond_to?(:secret_key_base) && key_exists?(@application.config)
@application.config.secret_key_base
end
@application.credentials
から @application.secrets
、@application.config
と順番にsecret_key_baseのキーを検証し、最初に見つかったものを使うようになっている様です。しかし、先述したとおり、credentialsは開発環境では使いませんし、secretsも廃止済みなのでいずれも値は何もセットされません。
また、configには普通は secret_key_base
は格納しないと思うので、ここにも値は存在しません。
本来であれば、全環境でsecret_key_baseを参照する場合、先述した Rails::Application.secret_key_base
を使うべきなのですが、ご覧の通り実装のミスがあるようです。(このSecretKeyFinder自体4.4.3で追加された物であり、5.2で十分なテストをしていなかったのかもしれませんね)
ただこの点については、既にPRがあり、マージもされています。PRのコメントによれば、雰囲気的には次のバージョン4.4.4 (or 4.5.0) で解消されそうです。
次期バージョンが出るまでの応急処置
最初に書いた様にDeviseのsecret_keyには任意の値をセットできるので、config/initialzers/devise.rbで Rails.application.secret_key_base
を明示的にセットしてあげれば良さそうです。
Devise.setup do |config|
# The secret key used by Devise. Devise uses this key to generate
# random tokens. Changing this key will render invalid all existing
# confirmation, reset password and unlock tokens in the database.
# Devise will use the `secret_key_base` as its `secret_key`
# by default. You can change it below and use your own secret key.
config.secret_key = Rails.application.secret_key_base # 4.4.4が出るまでの応急処置
# ...
end
最後に、Rails consoleできちんと設定されているか確認します:
$ rails c
Running via Spring preloader in process 1167
Loading development environment (Rails 5.2.0)
irb(main):001:0> Devise.secret_key
=> "fed42568a7c9da7d8f43ec1cd7e60b57"
コメントを残す