gem devise の Getting started 翻訳

18 min read読了の目安(約16300字

Google Chrome の 翻訳機能って便利ですよね。
私もあれがなかったら普段いろんなリファレンスとか読むのがもっと大変だったと思います。

ただ、コードブロックなども翻訳してしまうので
GitHub の README.md などはうまく翻訳されなくて
本文と訳文をいったりきたりしてなかなかに面倒です。

今回、Rails プロジェクトに devise を導入するにあたり
Getting started の手順をなかなか汲み取れなかったので良い感じに翻訳してみました。
主に DeepL の翻訳+筆者の意訳でまとめてます。
原文みながら翻訳みたい方は以下を Chrome で 日本語に翻訳 したりしながらみた方が早いです。

原文

heartcombo / devise | GitHub

https://github.com/heartcombo/devise#getting-started

Getting started

Devise 4.0はRails 4.1以降で動作します。Gemfileに以下の行を追加します。

gem 'devise'

その後、 bundle install を実行します。

次にジェネレーターを実行します。

$ rails generate devise:install

この時点で、コンソールにいくつかの指示が表示されます。
これらの指示の中で、各環境での Devise メーラーのデフォルト URL オプションを設定する必要があります。
以下は、config/environments/development.rb で設定可能なものです。

config.action_mailer.default_url_options = { host: 'localhost', port. 3000 }

ジェネレーターは、Deviseの設定オプションのすべてを説明するイニシャライザをインストールします。
これを見ておくことは必須です。
これが完了したら、ジェネレータを使用してモデルにDeviseを追加する準備ができています。

次のコマンドでは、MODELをアプリケーションのユーザに使用されるクラス名に置き換えます(よくあるのはUserですが、Adminにすることもできます)。これはモデルを作成し(モデルが存在しない場合)、デフォルトのDeviseモジュールで設定します。
ジェネレータはまた、config/routes.rb ファイルがDeviseコントローラを指すようにconfig/routes.rb ファイルを設定します。

$ rails generate devise MODEL

次に、確認可能やロック可能など、追加したい追加の設定オプションがないかMODELをチェックします。
オプションを追加する場合は、必ず移行ファイル(ORMがサポートしている場合はジェネレーターが作成したもの)を検査し、適切なセクションのコメントを解除してください。例えば、モデルにconfirmableオプションを追加した場合、マイグレーションのConfirmableセクションのコメントを外す必要があります。

次に、以下を実行します。

rails db:migrate

Deviseの設定オプションを変更した後、アプリケーションを再起動する必要があります(これにはspringの停止も含まれます)。そうしないと、ユーザーがログインできなかったり、ルートヘルパーが未定義だったりといった奇妙なエラーが発生します。

Controller filters and helpers

Devise は、コントローラやビューの内部で使用するためのヘルパーを作成します。ユーザー認証機能を持つコントローラを設定するには、次の before_action を追加します (devise モデルが 'User' であると仮定します)。

before_action :authenticate_user!

Rails 5では、protect_from_forgerybefore_actionチェーンの前に付加されなくなったため、protect_from_forgeryの前にauthenticate_userを設定していると、
リクエストの結果が "Can't verify CSRF token authenticity"になってしまうことに注意してください。これを解決するには、これらを呼び出す順番を変更するか、
protect_from_forgery prepend: true を使用します。

デバイスのモデルが User 以外の場合は、"_user" を "_yourmodel" に置き換えてください。
以下の説明にも同じロジックが適用されます。

ユーザーがサインインしているかどうかを確認するには、以下のヘルパーを使用します。

user_signed_in?

現在サインインしているユーザーに対しては、このヘルパーを使用できます。

current_user

このスコープのセッションにアクセスすることができます。

user_session

ユーザーにサインインした後、アカウントを確認した後、またはパスワードを更新した後、Devise はリダイレクト先のスコープされたルート・パスを探します。例えば、:user リソースを使用している場合、user_root_path が存在すれば使用され、そうでなければデフォルトの root_path が使用されます。これは、routes: の中にルートを設定する必要があることを意味します。

config/routes.rb
root to: 'home#index'

after_sign_in_path_forafter_sign_out_path_for をオーバーライドしてリダイレクトフックをカスタマイズすることもできます。
例えば Devise モデルが User ではなく Member と呼ばれている場合、利用可能なヘルパーは以下のようになります。

before_action :authenticate_member!

member_signed_in?

current_member

member_session

Configuring Models

モデルの Devise メソッドでは、モジュールを設定するためのオプションも用意されています。
たとえば、以下のようにハッシュアルゴリズムのcostを選ぶことができます。

devise :database_authenticatable, :registerable, :confirmable, :recoverable, stretches: 13

:stretches 以外にも、他のオプションとして、:pepper, :encryptor, :confirm_within, :remember_for, :timeout_in, :unlock_in を定義することができます。
詳細については、上で説明した devise:install ジェネレータを起動したときに作成されたイニシャライザファイルを参照してください。このファイルは通常 /config/initializers/devise.rb にあります。

Strong Parameters

Devise 4 で パラメータサニタイザーAPIが変更になりました。

以前のバージョンのDeviseについては、以下を参照してください。

独自のビューをカスタマイズすると、フォームに新しい属性を追加してしまうことがあります。Rails 4 では、パラメータのサニタイズをモデルからコントローラに移したため、Deviseでもこの問題をコントローラで処理するようになりました。
Deviseには、任意のパラメータセットをモデルに渡すことを許可するアクションが3つあるだけで、サニタイズが必要になります。これらのアクションの名前とデフォルトで許可されているパラメータは以下の通りです。

  • sign_in (Devise::SessionsController#create)
    • 認証キーのみを許可する (like email)
  • sign_up (Devise::RegistrationsController#create)
    • 認証キーと password, password_confirmation を許可する
  • account_update (Devise::RegistrationsController#update)
    • 認証キーと password, password_confirmation, current_password を許可する

追加のパラメータを許可したい場合 (レイジーウェイ™) は、ApplicationController のシンプルな before アクションで許可することができます。

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
  end
end

上記は、パラメータが単純なスカラ型である追加のフィールドに対しても動作します。ネストされた属性を持っている場合 (例えば accepts_nested_attributes_for を使用しているとします)、それらのネストと型について devise に伝える必要があります。

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name, address_attributes: [:country, :state, :city, :area, :postal_code]])
  end
end

Devise では、ブロックを渡すことで Devise のデフォルトを完全に変更したり、カスタム動作を呼び出すことができます。
ユーザー名と電子メールに単純なスカラ値を許可するには、以下のようにします。

def configure_permitted_parameters
  devise_parameter_sanitizer.permit(:sign_in) do |user_params|
    user_params.permit(:username, :email)
  end
end

ユーザーが登録時に取ることができる役割を表現するチェックボックスがいくつかある場合、ブラウザはそれらの選択されたチェックボックスを配列として送信します。配列はStrong Parametersが許可しているスカラ値の1つではないので、以下のようにDeviseを設定する必要があります。

def configure_permitted_parameters
  devise_parameter_sanitizer.permit(:sign_up) do |user_params|
    user_params.permit({ roles: [] }, :email, :password, :password_confirmation)
  end
end

許可されるスカラのリストと、ネストされたハッシュや配列で許可されるキーの宣言方法については以下を参照してください。

複数の Devise モデルを使用している場合、モデルごとに異なるパラメータサニタイザを設定したい場合があります。この場合は、Devise::ParameterSanitizer を継承し、独自のロジックを追加することをお勧めします。

class User::ParameterSanitizer < Devise::ParameterSanitizer
  def initialize(*)
    super
    permit(:sign_up, keys: [:username, :email])
  end
end

そして、それを使用するようにコントローラを設定します。

class ApplicationController < ActionController::Base
  protected

  def devise_parameter_sanitizer
    if resource_class == User
      User::ParameterSanitizer.new(User, :user, params)
    else
      super # Use the default one
    end
  end
end

上の例では、ユーザが :username:email の両方を指定することができます。パラメータを設定する方法としては、カスタムコントローラで上記の before フィルタを定義する方法があります。コントローラの設定方法やカスタマイズ方法については、以下のいくつかのセクションで詳しく説明します。

Configuring views

私たちは、認証を利用したアプリケーションを素早く開発できるように Devise を構築しました。しかし、カスタマイズが必要なときに邪魔になることはありません。

Deviseはエンジンなので、すべてのビューはgemの中にパッケージ化されています。これらのビューは使い始めるのに役立ちますが、しばらくすると変更したくなるかもしれません。そのような場合は、以下のジェネレータを呼び出すだけで、すべてのビューをアプリケーションにコピーしてくれます。

rails generate devise:views

アプリケーションに複数の Devise モデル (UserAdmin など) がある場合、Devise はすべてのモデルに同じビューを使用していることに気づくでしょう。幸いなことに、Devise はビューを簡単にカスタマイズする方法を提供しています。config/initializers/devise.rb ファイルの中で config.scoped_views = true を設定するだけです。

そうすると、users/sessions/newadmins/sessions/new のように役割に応じたビューを持つことができるようになります。スコープ内にビューが見つからない場合、Deviseはdevise/sessions/newのデフォルトビューを使用します。ジェネレーターを使ってスコープ付きのビューを生成することもできます。

rails generate devise:views users

registerableモジュールやconfirmableモジュールのように、いくつかのビューのセットだけを生成したい場合は、モジュールのリストを-vフラグでジェネレータに渡すことができます。

rails generate devise:views -v registrations confirmations

Configuring controllers

ビューレベルでのカスタマイズが不十分な場合は、以下の手順で各コントローラをカスタマイズすることができます。

1. スコープを必要とするジェネレータを使用してカスタムコントローラを作成します。

rails generate devise:controllers [scope]

スコープにusersを指定すると、app/controllers/users/ にコントローラが作成されます。そして、セッションコントローラは以下のようになります。

class Users::SessionsController < Devise::SessionsController
  # GET /resource/sign_in
  # def new
  #   super
  # end
  ...
end

(コントローラを指定するには -c フラグを使用します。
例: rails generate devise:controllers users -c=sessions)

2. ルータにこのコントローラを使用するように指示します。

devise_for :users, controllers: { sessions: 'users/sessions' }

3. devise/sessionsからusers/sessionsにビューをコピーします。

コントローラが変更されたので、devise/sessionsにあるデフォルトのビューは使用されません。

4. 最後に、目的のコントローラアクションを変更または拡張します。

コントローラのアクションを完全に上書きすることができます。

class Users::SessionsController < Devise::SessionsController
  def create
    # custom sign-in code
  end
end

あるいは、単純に新しい動作を追加することもできます。

class Users::SessionsController < Devise::SessionsController
  def create
    super do |resource|
      BackgroundWorker.trigger(resource)
    end
  end
end

これは、特定のアクション中にバックグラウンドジョブをトリガーしたり、イベントをログに記録したりするのに便利です。

Devise は、サインインが成功したか失敗したかをユーザーに知らせるためにフラッシュメッセージを使用していることを覚えておいてください。Devise は、アプリケーションが flash[:notice]flash[:alert] を適切に呼び出すことを期待しています。フラッシュハッシュ全体を表示せず、特定のキーのみを表示してください。状況によっては、Devise はフラッシュハッシュに :timedout キーを追加しますが、これは表示用ではありません。ハッシュ全体を印刷する場合は、このキーをハッシュから削除してください。

Configuring routes

Devise にはデフォルトのルートも含まれています。それらをカスタマイズする必要がある場合は、おそらく devise_for メソッドを使ってカスタマイズすることができるはずです。これは :class_name:path_prefix などのオプションを受け付けており、I18n 用のパス名を変更することもできます。

devise_for :users, path: 'auth', path_names: { sign_in: 'login', sign_out: 'logout', password: 'secret', confirmation: 'verification', unlock: 'unblock', registration: 'register', sign_up: 'cmon_let_me_in' }

詳細は必ず devise_for のドキュメントを確認してください。

より深いカスタマイズが必要な場合、例えば "/users/sign_in" 以外に "/sign_in" を許可するなど、通常のルートを作成して、ルータの devise_scope ブロックでラップするだけです。

devise_scope :user do
  get 'sign_in', to: 'devise/sessions#new'
end

このようにして、"/sign_in" にアクセスしたときにスコープ :user を使うように Devise に指示します。devise_scope もルータと同じようにエイリアスされていることに注意してください。

current_user のようなヘルパーメソッドを使うためには、ルートに devise_for を追加する必要があります。

devise_for :users, skip: :all

I18n

Devise は、フラッシュキー :notice:alert と合わせて、I18n を使用したフラッシュメッセージを使用します。アプリをカスタマイズするには、ロケールファイルを設定します。

en:
  devise:
    sessions:
      signed_in: 'Signed in successfully.'

routes で指定された単数形の名前を使って、設定したリソースに基づいて異なるメッセージを作成することもできます。

en:
  devise:
    sessions:
      user:
        signed_in: 'Welcome user, you are signed in.'
      admin:
        signed_in: 'Hello admin!'

Deviseメーラーでは、似たようなパターンで件名メッセージを作成しています。

en:
  devise:
    mailer:
      confirmation_instructions:
        subject: 'Hello everybody!'
        user_subject: 'Hello User! Please confirm your email'
      reset_password_instructions:
        subject: 'Reset instructions'

すべてのメッセージを確認するには、ロケールファイルを見てください。また、私たちのwikiにある多くの翻訳のうちの一つに興味があるかもしれません。

Devise コントローラは ApplicationController を継承します。アプリが複数のロケールを使用している場合は、必ず ApplicationController で I18n.locale を設定してください。

Test helpers

Devise には、コントローラと統合テスト用のテストヘルパーがいくつか含まれています。これらを使用するには、それぞれのモジュールをテストケース/仕様に含める必要があります。

Controller tests

コントローラテストでは、テストケースまたはその親である ActionController::TestCase スーパークラスに Devise::Test::IntegrationHelpers を含める必要があります。コントローラテストのスーパークラスが ActionDispatch::IntegrationTest に変更されたので、Rails のバージョンが 5 より前の場合は、代わりに Devise::Test::ControllerHelpers をインクルードしてください (詳細については、Integration tests のセクションを参照してください)。

class PostsControllerTest < ActionController::TestCase
  include Devise::Test::IntegrationHelpers # Rails >= 5
end
class PostsControllerTest < ActionController::TestCase
  include Devise::Test::ControllerHelpers # Rails < 5
end

RSpecを使用している場合は、spec/support/devise.rb、または spec/spec_helper.rbrspec-railsを使用している場合はspec/rails_helper.rb)の中に以下のように記述します。

RSpec.configure do |config|
  config.include Devise::Test::ControllerHelpers, type: :controller
  config.include Devise::Test::ControllerHelpers, type: :view
end

このインクルードは、require 'rspec/rails' ディレクティブの後で行われていることを確認してください。
これで、コントローラテストで sign_insign_out メソッドを使う準備ができました。

sign_in @user
sign_in @user, scope: :admin

Devise の内部コントローラや Devise を継承するコントローラをテストしている場合は、リクエストの前にどのマッピングを使用するかを Devise に伝える必要があります。Devise はこの情報をルータから取得しますが、コントローラのテストはルータを通過しないので、 明示的に指定する必要があります。たとえば、ユーザスコープをテストしているのであれば、単純に以下のようにします。

test 'GET new' do
  # Mimic the router behavior of setting the Devise scope through the env.
  @request.env['devise.mapping'] = Devise.mappings[:user]

  # Use the sign_in helper to sign in a fixture `User` record.
  sign_in users(:alice)

  get :new

  # assert something
end

Integration tests

統合テストヘルパーは、Devise::Test::IntegrationHelpers モジュールを含めることで利用できます。

class PostsTests < ActionDispatch::IntegrationTest
  include Devise::Test::IntegrationHelpers
end

これで、統合テストで以下の sign_insign_out メソッドを使うことができるようになりました。

sign_in users(:bob)
sign_in users(:bob), scope: :admin

sign_out :user

RSpecユーザはIntegrationHelpers モジュールを :feature specs に含めることができます。

RSpec.configure do |config|
  config.include Devise::Test::IntegrationHelpers, type: :feature
end

コントローラテストとは異なり、統合テストではdevise.mapping env値を指定する必要はありません。
RSpecを使ったRails 3 - Rails 4コントローラのテストについては、wikiを参照してください。

OmniAuth

Devise には、他のプロバイダとの認証を行うための OmniAuth サポートが付属しています。これを使用するには、config/initializers/devise.rb で OmniAuth の設定を指定するだけです。

config/initializers/devise.rb
config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'

OmniAuthのサポートについてはwikiで詳しく説明されています。

Configuring multiple models

Devise では、好きなだけ Devise モデルを設定することができます。上記の User モデルに加えて、認証とタイムアウト機能だけの Admin モデルを設定したい場合は、以下のように実行してください。

# 必要なフィールドでマイグレーションを作成します。
create_table :admins do |t|
  t.string :email
  t.string :encrypted_password
  t.timestamps null: false
end

# Admin モデルの内部
devise :database_authenticatable, :timeoutable

# routes に
devise_for :admins

# protected controller に
before_action :authenticate_admin!

# コントローラとビュー に
admin_signed_in?
current_admin
admin_session

あるいは、単にDeviseジェネレーターを実行することもできます。

それらのモデルには完全に異なるルートがあることを心に留めておいてください。サインインやサインアウトなどのために同じコントローラを共有することはできませんし、できません。異なるロールで同じアクションを共有させたい場合は、ロールカラムを提供するか、認証用の専用gemを使用してロールベースのアプローチを使用することをお勧めします。

ActiveJob Integration

キューイングバックエンドを通してバックグラウンドでActionMailerメッセージを配信するためにRails 4.2とActiveJobを使用している場合、モデル内のsend_devise_notificationメソッドをオーバーライドすることで、既存のキューを通してDeviseメールを送信することができます。

def send_devise_notification(notification, *args)
  devise_mailer.send(notification, self, *args).deliver_later
end

Password reset tokens and Rails logs

Recoverable モジュールを有効にしている場合、盗まれたパスワードリセットトークンが攻撃者にあなたのアプリケーションにアクセスされる可能性があることに注意してください。Deviseはランダムで安全なトークンを生成するために努力しており、トークンのダイジェストのみをデータベースに保存し、プレーンテキストは保存しません。しかし、Railsのデフォルトのロギング動作により、プレーンテキストトークンがログファイルに漏れてしまう可能性があります。

  • Action Mailerは、すべての送信メールの内容をDEBUGレベルに丸ごとログに記録します。電子メールでユーザーに配信されたパスワードリセットトークンが漏洩します。
  • アクティブジョブは、すべてのエンキューされたジョブへのすべての引数を INFO レベルでログに記録します。パスワード リセット メールを送信するために deliver_later を使用するように Devise を設定すると、パスワード リセット トークンが漏洩します。

Railsでは、本番用ロガーのレベルがデフォルトでDEBUGに設定されています。トークンがログに漏れないようにしたい場合は、プロダクションロガーのレベルをWARNに変更することを検討してください。

config/environments/production.rb
config.log_level = :warn

Other ORMs

Devise は ActiveRecord (デフォルト) と Mongoid をサポートしています。他の ORM を選択するには、初期化ファイルでそれを要求するだけです。

Rails API Mode

Rails 5+にはAPIとして使用するためにRailsを最適化するAPIモードが組み込まれています(APIモードのみ)。Deviseは、例外などを発生させないという意味で、このモードで構築されたアプリケーションを追加の修正なしにある程度扱えるようになっています。しかし、この互換性の完全な範囲がまだわかっていないため、開発/テスト中にいくつかの問題が発生するかもしれません。(詳細は issue #4947 を参照してください)

サポートされている認証戦略

API のみのアプリケーションは、クッキーによるブラウザベースの認証をサポートしていません。しかし、devise は HTTP Basic Auth を使用し、各リクエストでユーザを認証する http_authenticatable 戦略を使用して、そのような場合でもすぐに認証を提供することができます。(詳細については、How To: Use HTTP Basic Authentication のwiki記事を参照してください)

HTTP Auth のデバイスのデフォルトは無効になっているので、データベースストラテジーのデバイス初期化で有効にする必要があります。

config.http_authenticatable = [:database]

この制限は、アプリケーション内で、または gem ベースの拡張機能を使用してデバイスにカスタムウォーデン戦略を実装することを制限するものではありません。API の一般的な認証ストラテジーはトークンベースの認証です。このタイプの認証やその他の認証をサポートするためにdeviseを拡張することの詳細については、シンプルなトークン認証の例と代替案についてのwiki記事や、Deviseを使ったカスタム認証方法についてのこのブログ記事を参照してください。

Testing

APIモードではミドルウェアスタックの順番が変わるため、Devise::Test::IntegrationHelpers で問題が発生する可能性があります。この問題は通常、#sign_in のような統合テストヘルパーを使用している場合、nil:NilClass のための未定義メソッド ``[]='` エラーとして表面化します。
解決策は、以下を test.rb に追加してミドルウェアを並べ替えるだけです。

Rails.application.config.middleware.insert_before Warden::Manager, ActionDispatch::Cookies
Rails.application.config.middleware.insert_before Warden::Manager, ActionDispatch::Session::CookieStore

これについての理解を深めるには、この問題を確認してください。

さらに、ビューがサポートされていない場合、現時点では、Confirmable、Recoverable、Lockableからの一部のメールベースのフローは直接サポートされていないことに注意してください。

以上

以上です。結構難解な Getting started ですね……。
網羅性が高い分、使い始めるのには不要な情報が多いというか……😭