この記事はマネーフォワード アドベントカレンダー21日目の記事です。
Railsエンジニアの @kamillle です。
今回はRack middlewareでBasic認証をRailsのアプリケーションに組み込んでみました。
RailsでBasic認証をどうやって実装するか
Googleで rails basic auth
とググると api.rubyonrails.org
のドキュメントがヒットし、http_basic_authenticate_with
にnameやpasswordというkey名を含んだHashを渡すと、そのコントローラーのアクションに対してBasic認証がかけられることがわかりました。
一行でかけるのでRailsぽくていいなーと思ったですが、同時に下記の違和感を覚えました。
- リクエストがコントローラーに来る前に解決すべきことなのかなと思った
- コントローラーの継承関係を間違うと一部のControllerにBasic認証がかかってなかったってことが起きそう
- テスト書いておけばいいんですが、全コントローラーのrequest specにBasic認証のテスト書くのもめんどくさい、忘れそう
Rackがあるじゃないか
Rack middlewareでBasic認証の機構を実装し、 @app.call(env)
する前にBasic認証することができればRack側で処理が完結するためコントローラーにリクエストが行くこともないし、全エンドポイントに対してBasic認証をかけることができるので、アプリケーションの開発時に認証のことを気にする必要がなくなるんじゃないかなと考えました。
Basic認証のロジックをオレオレで実装したくなかったので、 Rack basic auth
とかでググってると Rack::Auth::Basic
というクラスを見つけました。
このクラスはRFCに沿った形でBasic認証を実装してくれているので、オレオレなことをしなくていいし、使うのも簡単にできそうだったので早速やってみました。
このようなRack middlewareを実装し、
# lib/middlewares/basic_auth_middleware.rb
module Middlewares
class BasicAuthMiddleware
def initialize(app)
@app = app
end
def call(env)
Rack::Auth::Basic.new(@app) do |username, password|
username == 'username' && password == 'password'
end.call(env)
end
end
end
それをミドルウェアスタックに追加しました
# config/application.rb
...
require_relative '../lib/middlewares/basic_auth_middleware.rb'
...
module RackBasicAuth
class Application < Rails::Application
...
...
config.middleware.use(::Middlewares::BasicAuthMiddleware)
end
end
ここまで実装してRailsのサーバーを立ち上げてみました
今回Basic認証を実装したアプリケーションは下記のリポジトリで公開しています。
https://github.com/kamillle/rack_basic_auth
おわりに
Rack::Auth::Basicを使うことでも簡単にBasic認証をアプリケーションに組み込むことができました。
今までRack middlewareを実装したことはなかったのですが、Rails側にリクエストを渡す前にリクエストの解析が可能になるので便利だな〜と思いました(弊社のGitHubのOrganization内のコードを見てみると、一部のエンドポイントにのみIP制限をかけるためにRackを使っているプロジェクトがありました)。