Railsの認証Gem、Deviseとはなんなのか
目的
deivseについての理解を深めて、デフォルト動作ではない認証を作れるようにする。
背景
用意されすぎているライブラリは苦手。 挙動を変えたい場合に出来ること、出来ないことがわかりにくい。 理解したいってこと。
deviseとは
RailsEngineで作られている、Railsの認証ライブラリ。 サクっと認証を用意できる。ために、色々とデフォルト動作が決まっている。 手厚いサポート(機能)があるが、実際そんなにいらないことも多いはず。 ただ、有名すぎてこれを使っちゃうだろう。 wardenをみたあとでは、別にwardenでもいいよね。って気持ちはある。
だがデフォルトの動作を理解し自分の必要な部分だけを残し、ほかはいい感じにカスタマイズが出来るのであれが、deviseを使ったほうが楽だろうなというところ。 それが可能か検証する。
defaultで含まれているモジュール
- Database Authenticatable
- 一般的なやつ。passwordで認証をする。DBには暗号化されたパスワードを保存してる
- Omniauthable
- omniAuth gemとの連携
- confirmable
- 登録時に、確認メールを送る。ログイン時に確認済がどうかを判断する?
- Recoverable
- パスワードリセット
- Registerable
- 登録、編集、削除
- Rememberable
- ユーザーを覚えておくためのトークンを発行したりする。
- remember me のやつ
- Trackable
- サインインしたときのユーザーのIPとかとっておく
- Timeoutable
- 時間内にアクティブではないユーザーのセッションを期限切れにする
- Validatable
- mailアドレスとパスワードで認証するってことかな?
- Lockable
- 指定回数、ログインに失敗したらアカウントをロックする
揃っている。 これらは、(User)モデルで使用するかどうかを指定できるため、簡単につけ外しができる。 ※Tableにカラムがあれば。カラムを消したりしたりすると簡単ではないが(めんどい)。
devise module
Devise.add_moduleメソッドでモジュールを登録できる。 optionは以下。
※以下のリストは正直全然わかってない。
- model
- moduleで使用するモデルへのぱす
- controller
- moduleで使うコントローラー
- route
- moduleの使う route helper
- strategy
- strategyを持っているか。 true or false
- insert_at
- moduleが含まれる位置
model以外はすべて bool 値でもよく、module名と同じになる。
Deviseの既存のモジュールは lib/devise/modules.rb
で読み込まれている。
Devise.with_options model: true do |d| # Strategies first d.with_options strategy: true do |s| routes = [nil, :new, :destroy] s.add_module :database_authenticatable, controller: :sessions, route: { session: routes } s.add_module :rememberable, no_input: true end # Other authentications d.add_module :omniauthable, controller: :omniauth_callbacks, route: :omniauth_callback # Misc after routes = [nil, :new, :edit] d.add_module :recoverable, controller: :passwords, route: { password: routes } d.add_module :registerable, controller: :registrations, route: { registration: (routes << :cancel) } d.add_module :validatable # The ones which can sign out after routes = [nil, :new] d.add_module :confirmable, controller: :confirmations, route: { confirmation: routes } d.add_module :lockable, controller: :unlocks, route: { unlock: routes } d.add_module :timeoutable # Stats for last, so we make sure the user is really signed in d.add_module :trackable end
これらは、(User)modelで devise メソッドで定義することで使用できるようになる。
devise :database_authenticatable, :trackable, :omniauthable, omniauth_providers: [:google_oauth2]
このdeviseメソッドはどこからきているのか。
lib/devise/orm/active_record.rb
で、active_recordが読み込まれたタイミングのcallbackで、Devise::Models
が extends されている。
require 'orm_adapter/adapters/active_record' ActiveSupport.on_load(:active_record) do extend Devise::Models end
deviseメソッドはなにをしているか。
include Devise::Models::Authenticatable
Models::Authenticatableクラスのinclude
deviseメソッドで指定されたモジュール群の、 include
ClassMethods
が定義されていた場合、 extends
もする。
たとえば、 database_authenticatable
の場合だとこんな感じで定義されていて、
Devise::Models.config
を呼んで、find_for_database_authentication
をモデルに定義する。
module ClassMethods Devise::Models.config(self, :pepper, :stretches, :send_email_changed_notification, :send_password_change_notification) # We assume this method already gets the sanitized values from the # DatabaseAuthenticatable strategy. If you are using this method on # your own, be sure to sanitize the conditions hash to only include # the proper fields. def find_for_database_authentication(conditions) find_for_authentication(conditions) end end
このモデルに定義したモジュールたちがどう使われるのか。を見ていきたい。
lib/devise/rails/routes.rb
の devise_for
メソッドから見ていく。
このメソッドは config/routes.rb
で以下のような形で呼ぶことになる。
devise_for :users, controllers: { sessions: 'users/sessions' }
devise_forの責任としては、指定したリソース(モデル)を使用した認証関連のルーティングの設定をすること。
devise_for :users
とだけ定義した場合は、 User
モデルを見て、ルーティングを設定する。
ルーティングの他は Devise::Mapping
の生成をする。
mappingはscopeごとに生成され、以下のようなものになる。
mapping = Devise.mappings[:user] mapping.name #=> :user mapping.as #=> "users" mapping.to #=> User mapping.modules #=> [:authenticatable]
認証ロジックを自作したい。
ここまで見てきたわかったことは、 deviseに処理を追加したい場合は、モジュールを作る。 モジュールには、スコープのモデルに追加したいメソッド、 スコープを使用してのルーティングの追加、 ルーティングに使用するコントローラーの追加、 スコープを認証する際のstrategyの定義ができる。
単純に考えると、モジュールを作るかー。となりそうなものだが、
database_authenticatable
を継承したりしていい感じに出来ないの?って思ったりする。
そこらへんを見ていきたい。
モデルに対して、 database_authenticatable
を deviseメソッドで指定すると、以下のルーティングが作成される。
new_user_session GET /sign_in(.:format) devise/sessions#new user_session POST /sign_in(.:format) devise/sessions#create destroy_user_session DELETE /sign_out(.:format) devise/sessions#destroy
Deviseに用意されているControllerは DeviseController
を継承している。
DeviseController
は ApplicationController
を継承している。
DeviseControllerは warden
へアクセスするヘルパーメソッドが用意されている。
自作するために。
strategyモジュールを作って、モデルに読み込ませる。
認証をするControllerでDeviseControllerを継承し、 warden.authenticate
, sign_in
等のヘルパーメソッドを呼ぶ。
こんな感じ。
というかこれだけの事をするのに devise
が必要か?というといらない。
devise とはなんだったのか
wardenのヘルパー。
deviseの決めた挙動での認証システムの高速な実装。
strategy, routing等まで含めた認証実装の簡易化。
たとえば、社内システムでSSOを実装する。となった場合に、1度実装すれば簡単に使いまわすことが可能。 という感じだろうか。
まとめ
読んでいないソースが大半なので、確定ではないが、 deviseの標準を大きく外れるようであれば、無理にdeviseを使う必要もないとう判断になった。 wardenで十分ぽい。