Railsで認証機能を自作する?それともDeviseを使う?

定期的にDevise批判の話が出てくるので、個人的な考えを書いてみます。

Railsに詳しくないなら、Deviseを使わないべきか?

認証自作、 Rails 、 Devise」の記事で以下のような記載がある。

「Rails について深い理解がないならば、 Devise は使うな」とあります。この方針は10 年近く前から書かれています。

これ元の英語とあってない気がするんですよね。

If you are building your first Rails application, we recommend you do not use Devise. Devise requires a good understanding of the Rails Framework. In such cases, we advise you to start a simple authentication system from scratch. Here's a few resources that should help you get started:

- Michael Hartl's online book: https://www.railstutorial.org/book/modeling_users
- Ryan Bates' Railscasts: http://railscasts.com/episodes/250-authentication-from-scratch and http://railscasts.com/episodes/250-authentication-from-scratch-revised
- Codecademy's Ruby on Rails: Authentication and Authorization: https://www.codecademy.com/learn/rails-auth

Once you have solidified your understanding of Rails and authentication mechanisms, we assure you Devise will be very pleasant to work with. 😃

私には「最初のRailsアプリではDeviseを使わず、シンプルな認証機能を自作して理解を深めるのを勧める」「Railsと認証メカニズムの理解を深めたら、Deviseが快適に動くのが分かるよ」くらいの内容に読めました。1

IPアドレスが漏れる危険性がある?

試してみた。

require "devise/version"
Devise::VERSION
#=> "4.7.2"
user = User.create(email: 'a@example.com', password: 'password')
user.to_json
#=> => "{\"id\":1,\"email\":\"a@example.com\",\"created_at\":\"2020-08-16T08:31:36.558Z\",\"updated_at\":\"2020-08-16T08:31:36.558Z\"}"
user.attributes.to_json
#=> "{\"id\":1,\"email\":\"a@example.com\",\"encrypted_password\":\"$2a$12$ECNX7kXJQKMSBp8xDcy5neVs/2NeuTdcqVnPyUbcy8fp.p4EWfBa6\",\"reset_password_token\":null,\"reset_password_sent_at\":null,\"remember_created_at\":null,\"sign_in_count\":0,\"current_sign_in_at\":null,\"last_sign_in_at\":null,\"current_sign_in_ip\":null,\"last_sign_in_ip\":null,\"confirmation_token\":null,\"confirmed_at\":null,\"confirmation_sent_at\":null,\"unconfirmed_email\":null,\"failed_attempts\":0,\"unlock_token\":null,\"locked_at\":null,\"created_at\":\"2020-08-16T08:31:36.558Z\",\"updated_at\":\"2020-08-16T08:31:36.558Z\"}"

attributesメソッドには全属性が含まれちゃうので、漏れる可能性はありそう。

ただ、これDeviseの問題なのかな...。
IPアドレスがDBに保存されている場合2、認証を自作してたとしても実装ミスると漏れるときは漏れると思うんですよね。

テストコードで防ぐ

Twitterはてブで「レビューで防ぐべき」とかあったけど、レビューのような人依存な対策だと漏れがあるので、テストコード書くのが良いかなと思いました。

# spec/rails_helper.rb
RSpec.configure do |config|
  # html/jsonに含まれてたらヤバそうな文字列を列挙しておく
  secrets = %w[password lastSignInIp]

  config.after(type: :request) do |ex|
    secrets.each { |secret| expect(response.body).not_to include(secret) }
  end
  config.after(type: :system) do |ex|
    secrets.each { |secret| expect(page.body).not_to include(secret) }
  end
end

カバレッジがある程度高ければ、上のコードでだいたい防げるんじゃないかなと。

機能が少ないので自作の方がメンテしやすい?

個人的な経験ですが、認証機能が少ないままで済んだ事がないです。

  • パスワードリセットが必要
  • パスワード失敗回数でロックしたい
  • KPIのためにログイン回数が知りたい
  • Facebook, Twitterログインが使いたい

のような要望が後から出ることが多い。

特に認証機能はプロジェクト初期に作られるためテストコードが不足していたり、誰も壊したくないので条件分岐が雑に増えていってAbcSizeが高くなる状況が生まれやすいです。

個人的な見解

認証機能を自作する自信がないなら、最初からDeviseを使っておくのが無難じゃないかなーと思ってます。

お金あるなら Deviseを使い慣れているフリーランス技術顧問 してる人と短期の契約をして、相談するのも良いと思います。

認証機能を自作するなら

少なくとも以下の調査はしておいた方が良いです。

  • Deviseのコード内にあるセキュリティ対策のコメントを調査する
    • timing attacks, session fixation attacks, ...etc
  • Deviseのリダイレクト関連の挙動を調査する
    • (認証が必要な)ページA => ログイン => ページA の画面遷移の処理
    • パスワードリセットのURLを踏んだときのリダイレクト先3
  • Deviseのテストヘルパーの挙動を調査する
    • HTTPリクエストを投げずにログイン状態を再現するので速い4
  • Deviseのモジュールと同じ要件がきた時のことを考慮する
    • アカウントロックとか後から出てきがち
  • 他gemが提供するDevise連携を使えない事を理解する

Deviseにあるような機能は将来に必要になる可能性が高いので、ある程度は考慮しておいた方が良いです。

あと、Deviseを避けて認証機能を自作した結果、Deviseが何年も前に対応済みの脆弱性を再実装するのは勿体ないです。

まとめ

Deviseが最高の認証ライブラリとは思ってないけど、十分に便利なライブラリだとは思ってます。

認証機能のセキュリティをちゃんと考えて実装できる人はさておき、Rails初心者〜中級者ならDeviseを使うでも良いのかなと。


  1. 英語力は自信ないので、各自でちゃんと英語を読んでほしい。

  2. アクセス解析、不正なログインの検知などでIPアドレスをDBに保存することはある。

  3. パスワードリセットURLを踏んで、自動ログイン…とか書かないように。メアド間違えたら、他人がログインできてしまう。

  4. Sorceryのテストヘルパーはリクエスト投げてて遅い🐢