久保田です。
最近、deviseのストラテジー(プラグイン的なもの)を社内の認証システム用に作りました。
その時、古い情報が多く、半泣きになりながらいろいろ模索して作ったので、
備忘録がてら、同じく困る人がいなくなるように記事にします。
devise
Railsで認証つきのアプリケーションを作成したことのある人なら必ず使ったことのあるgemだと思います。
かなり充実したライブラリでデフォルトでよくあるパターンのストラテジーは入っているので、基本的にはそのまま使うことができます。
ストラテジー
deviseではストラテジーという認証のルールを設定することで、必要な機能だけを享受することができます。
例えば、db認証をやりたい時は、
class User < ApplicationRecord devise :database_authenticatable # ... end
ロック機能をつけたい時は、
class User < ApplicationRecord devise :database_authenticatable, :lockable # ... end
のような感じで宣言します。
今回は、このストラテジーをオリジナルで作ったから作り方を共有します!という話です。
完成イメージ
完成イメージは以下のような感じです。
class User < ApplicationRecord devise :foo_authenticatable # ... end
foo_authenticatableというストラテジーを作成します。
これだけで、いつものようにdeviseを使うようにしてオリジナルの認証を通します。
フォルダ構成
最低限必要な構成は以下の通りです。
(lib以下に作りました。)
lib └── devise └── models └── foo_authenticatable.rb └── strategies └── foo_authenticatable.rb
ストラテジーを作るためには、
- modelがincludeするファイル
- 実際に認証を行うstrategyファイル
が必要です。
動作イメージ
動作イメージは、以下のような流れになります。
sessions_controller#create ↓ strategies/foo_authenticatable#authenticate!(成功か失敗かを判断する) ↓ models/foo_authenticatable.独自のロジック(必要に応じて)
strategy
まずはstrategyファイルからです。
このファイルは、実際に認証を通していいか、ダメかを判定するロジックを書きます。
以下のような構成になります。
- strategies/foo_authenticatable.rb
require 'devise/strategies/authenticatable' module Devise module Strategies class FooAuthenticatable < Authenticatable # optional def valid? # 事前に行いたいバリデーション。 # パラメータなどの確認を行う。 # ex: params['username'] || params['password'] end # required def authenticate! # modelで定義されたメソッドで認証ロジックを通す。modelに書いた方がスッキリするため。 # mapping.toでストラテジーを宣言したモデルが取得できる。 res = mapping.to.authenticate(params[scope]) if res success!(res) # 認証成功。 else fail!(:invalid) # 認証失敗。 end end end end end Warden::Strategies.add(:foo_authenticatable, Devise::Strategies::FooAuthenticatable) # ストラテジーの登録
定義されているメソッドは2つ、valid?
とauthenticate!
です。
valid?
はオプショナルなメソッドなので、あってもなくても大丈夫です。あった場合はauthenticate!
の前に動き、
結果がtrueの場合だけauthenticate!
を実行します。
authenticate!
は必須のメソッドです。
authenticate!
の中でsuccess!
もしくはfail!
を呼び出すことで認証の判定が可能になります。
他のメソッドはこちらです。
これだけで、認証の判定は完成です。
次にmodelに任せた認証ロジックの部分です。
model
次はmodelにロジックを作ります。
modelは最初なくても大丈夫かなーと思っていましたが、ないとないよーってエラーを出しているようでしたので、どうやらいるようです。
そしてこのmodel、自動的にこのストラテジーをdevise :foo_authenticatable
のように宣言したモデルにincludeされるようです。
なので、このmodelに実際のロジックを書いていきます。
- models/foo_authenticatable.rb
require Rails.root.join('lib/devise/strategies/foo_authenticatable') module Devise module Models module FooAuthenticatable extend ActiveSupport::Concern module ClassMethods def authenticate(attributes) # 認証ロジック # 仮にデータベースのidで認証 res = find_by(id: attributes[:id]) if res.present? res else nil end end end end end end
最低限はこんな感じです。
extend ActiveSupport::Concern
をし、module ClassMethods
を使うと簡単にクラスメソッドが定義できるので、綺麗ですね。
認証が成功したら、インスタンスを返してあげます。後ほどそれがstrategyのsuccess!
に渡され、認証ユーザーとなります。
もしaccessorなどを足したい時はincluded
を使ってあげましょう。
initializer
最後にinitializer/devise.rbに独自のauthenticationを登録するための処理を書きます。
- config/initializer/devise.rb
Devise.setup do |config| config.warden do |manager| manager.default_strategies(scope: :user).unshift :foo_authenticatable end # ... end Devise.add_module(:foo_authenticatable, { strategy: true, controller: :sessions, model: 'devise/models/foo_authenticatable', route: :session, })
これで完了です。
もし独自のストラテジーを作りたくなったらやってみてください。