Railsで行うBasic認証

Rails3.1以前は authenticate_or_request_with_http_basic メソッドをbefore_filter内で呼ぶなりして実装していたがRails3.1にて http_basic_authenticate_with クラスメソッドが追加された。
#270 Authentication in Rails 3.1 - RailsCasts

ActionController::HttpAuthentication::Basic
シンプルなBasic認証を実装するのであればわざわざ認証用メソッドを作らなくても http_basic_authenticate_with を使えばbefore_actionに設定してくれる。

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 68
          def http_basic_authenticate_with(options = {})
            before_action(options.except(:name, :password, :realm)) do
              authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
                name == options[:name] && password == options[:password]
              end
            end
          end
http_basic_authenticate_with (ActionController::HttpAuthentication::Basic::ControllerMethods::ClassMethods) - APIdock

Rails4.2+Deviseでproductionで起動するとDevise.secret_key was not set.

developmentだと問題なく動いていたがproductionで動かしてみたら掲題の通りの例外が発生。
このメッセージを表示している構文はこうなっている。掲示されているsecret_keyはランダム生成されているものだとわかる。

      def raise_no_secret_key #:nodoc:
        raise <<-ERROR
Devise.secret_key was not set. Please add the following to your Devise initializer:
  config.secret_key = '#{SecureRandom.hex(64)}'
Please ensure you restarted your application after installing Devise or setting the key.
ERROR
      end
https://github.com/plataformatec/devise/blob/master/lib/devise/rails/routes.rb

じゃあ、なぜdevelopmentだと問題ないのにproductionだと例外が発生するのかというと。

Devise 3.2.3からRails4以降を使用している場合且つ、config/initializers/devise.rbに独自のsecret_keyが設定されていない場合secret_key_baseをベースとしてtokenを生成するようになった。

3.2.3 - 2014-02-20

  • enhancements
    • Devise will use the secret_key_base on Rails 4+ applications as its secret_key. You can change this and use your own secret by changing the devise.rb initializer.

該当する処理はこんな感じ。

    initializer "devise.secret_key" do |app|
      if app.respond_to?(:secrets)
        Devise.secret_key ||= app.secrets.secret_key_base
      elsif app.config.respond_to?(:secret_key_base)
        Devise.secret_key ||= app.config.secret_key_base
      end

      Devise.token_generator ||=
        if secret_key = Devise.secret_key
          Devise::TokenGenerator.new(
            Devise::CachingKeyGenerator.new(Devise::KeyGenerator.new(secret_key))
          )
        end
    end
https://github.com/plataformatec/devise/blob/master/lib/devise/rails.rb

devise.rbをgenerateした段階ではconfig.secret_keyはコメントアウトされていたのでsecret_key_baseを使用している状態。

肝心のsecret_key_baseはどこで設定されているかというとconfig/secrets.ymlで設定されている。
これはRails4.1から導入された管理方法で、productionの項を見れば原因がわかると思う。

# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

productionの場合環境変数で設定するんだけど怠ったのでsecret_keyが設定されず、devise.rbでも設定していないので未設定の例外が発生していたという訳であった。

Rails4.2 Webrickでproduction環境動作させるには

本番環境でWebrickで運用することはまず無いと思うが、開発時にproduction環境で動作を確認したいことがあったが少し設定の必要があった。
まずAsset Pipelineを使用するのでrake assets:precompileをしておく必要がある。
ApacheやNginxで動作させる場合には静的コンテンツはそれらWebサーバを介して配信されるので問題ないが、Webrick(に限らずRailsアプリケーションサーバ単独)で動作させる場合には静的コンテンツをRailsで扱う設定が必要になる。

Rails4.2より前はserve_static_assetsという設定で管理されていたが、Rails4.2になりserve_static_filesに変更になりserve_static_assetsは非推奨になっておりRails5.0で削除予定設定しているとWARNINGが出る。

DEPRECATION WARNING: The configuration option `config.serve_static_assets` has been renamed to `config.serve_static_files` to clarify its role (it merely enables serving everything in the `public` folder and is unrelated to the asset pipeline). The `serve_static_assets` alias will be removed in Rails 5.0. Please migrate your configuration files accordingly.

config/environments/production.rbに設定は記載されており、Rails4.2での設定値は下記の様になっている。

  # Disable serving static files from the `/public` folder by default since
  # Apache or NGINX already handles this.
  config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?

環境変数で値を設定するようになっている。これがtrueになっている時Webrickで静的ファイルが扱えるので画像の表示等ができるようになる。