Quantcast
Channel: hachi8833の記事一覧|TechRacho by BPS株式会社
Viewing all articles
Browse latest Browse all 1765

Rails: 認証gem ‘rodauth-rails’ README(翻訳)

$
0
0

概要

MITライセンスに基づいて翻訳・公開いたします。

見出しの深さは変えてあります。

rodauth-rails作者のjanko氏による以下の記事もどうぞ。

Rails: 認証gem ‘Rodauth’を統合するrodauth-railsを開発しました(翻訳)

Rails: 認証gem ‘rodauth-rails’ README(翻訳)

janko/rodauth-rails - GitHub

rodauth-railsは、Rodauth認証フレームワークのRails統合を提供します。

🔗 Rodauthを使う理由

Railsには、既に有名な認証ソリューションがいくつもあります(Devise、Sorcery、Clearance、Authlogic)が、Rodauthを選ぶ理由は何でしょうか?特に大きな理由を以下に示します。

Railsの他の認証フレームワークと異なり、RodauthがデータベースとのやりとりにActive RecordではなくSequelを使っていることをよく懸念されますが、これにはれっきとした理由がいくつもあります。rodauth-railsは、RodauthをActive Recordとスムーズに連携させるために、SequelがActive Recordのデータベースコネクションを再利用する形でSequelを設定します。

🔗 インストール

プロジェクトにrodauth-rails gemを追加します。

$ bundle add rodauth-rails

続いてインストールジェネレータを実行します。

$ rails generate rodauth:install

または、RodauthのエンドポイントをJSON APIで公開したい場合は以下を実行します。

# Railsセッションを用いる通常の認証
$ rails generate rodauth:install --json 

# または

# "Authentication"ヘッダー経由のトークン認証
$ rails generate rodauth:install --jwt
$ bundle add jwt

このジェネレータは以下を作成します。

  • 一般的な認証機能を有効にしたRodauthアプリと設定ファイル
  • これらの機能で必要なテーブルを含むデータベースマイグレーション
  • デフォルトのテンプレートを含むメーラー
  • その他のファイル数個

使わない機能は、対応するテーブルとともに自由に削除できます。

続いてマイグレーションを実行します。

$ rails db:migrate

メーラーでメールのリンクを生成可能にするために、環境ごとにデフォルトのURLオプションを指定する必要があります。以下はconfig/environments/development.rbで利用可能な設定です。

# config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

🔗 利用法

🔗 ルーティング

Rodauthのエンドポイントは(Railsのコントローラではなく)Rackミドルウェアで処理されるので、Rodauthのルーティングはrails routesでは表示されません。

現在読み込まれている機能に基づいたエンドポイントのリストを表示するには、rodauth:routes rakeタスクを使います。

$ rails rodauth:routes
Routes handled by RodauthApp:

  GET/POST  /login                   rodauth.login_path
  GET/POST  /create-account          rodauth.create_account_path
  GET/POST  /verify-account-resend   rodauth.verify_account_resend_path
  GET/POST  /verify-account          rodauth.verify_account_path
  GET/POST  /change-password         rodauth.change_password_path
  GET/POST  /change-login            rodauth.change_login_path
  GET/POST  /logout                  rodauth.logout_path
  GET/POST  /remember                rodauth.remember_path
  GET/POST  /reset-password-request  rodauth.reset_password_request_path
  GET/POST  /reset-password          rodauth.reset_password_path
  GET/POST  /verify-login-change     rodauth.verify_login_change_path
  GET/POST  /close-account           rodauth.close_account_path

この情報を元に、アプリのナビゲーションで以下のように基本的な認証をいくつか追加できます。

<% if rodauth.logged_in? %>
  <%= link_to "Sign out", rodauth.logout_path, method: :post %>
<% else %>
  <%= link_to "Sign in", rodauth.login_path %>
  <%= link_to "Sign up", rodauth.create_account_path %>
<% end %>

これらのルーティングは完全に機能するので、自由にアクセスしてページを移動してみてください。Rodauthに付属するテンプレートは完全な認証を体験可能にすることを目的としており、フォームではBootstrapのマークアップが使われています。

🔗 現在のアカウント

Rodauthオブジェクトは#rails_accountメソッドを定義します。このメソッドはアカウントに現在ログインしているアカウントのモデルインスタンスを返します。コントローラやビューから手軽にアクセスできるヘルパーメソッドを作成できます。

class ApplicationController < ActionController::Base
  private

  def current_account
    rodauth.rails_account
  end
  helper_method :current_account # ActionController::APIから継承する場合はスキップ
end
current_account #=> #<Account id=123 email="user@example.com">
current_account.email #=> "user@example.com"

セッションがログインされているがデータベースにアカウントが存在しない場合は、セッションがリセットされます。

🔗 アカウントモデルをカスタマイズする

#rails_accountメソッドは、設定済みのテーブル名からアカウントモデルクラスの推測を試みます。たとえば、accounts_table:usersに設定されている場合は、自動的にUserというモデルクラスを想定します。

ただし、テーブル名からモデルクラスを推論できない場合は手動で設定できます。

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    # ...
    rails_account_model { Authentication::Account } # カスタムモデル名
  end
end

🔗 認証を必須にする

アプリの特定の部分で認証を要求し、ユーザーがログインしていない場合はログインページにリダイレクトしたいことがあります。これはRodauthアプリのルーティングブロックで実現でき、認証ロジックをカプセル化するうえで有用です。

# app/misc/rodauth_app.rb
class RodauthApp < Rodauth::Rails::App
  # ...
  route do |r|
    # ...
    r.rodauth # rodauthリクエストのルーティング

    # /dashboard/*と/account/*で認証を要求する
    if r.path.start_with?("/dashboard") || r.path.start_with?("/account")
      rodauth.require_account # 認証されていない場合はログインページにリダイレクトする
    end
  end
end

認証は以下のようにコントローラ層でも要求できます。

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  private

  def authenticate
    rodauth.require_account # 認証されていない場合はログインページにリダイレクトする
  end
end
# app/controllers/dashboard_controller.rb
class DashboardController < ApplicationController
  before_action :authenticate
end
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :authenticate, except: [:index, :show]
end
🔗 ルーティングのconstraintsで認証する

場合によってはRailsルーターレベルで認証を要求する方が理にかなっていることもあります。これは組み込みのauthenticatedルーティング制約でできます。

# config/routes.rb
Rails.application.routes.draw do
  constraints Rodauth::Rails.authenticated do
    # ... 認証済みのルーティング ...
  end
end

条件を追加したい場合は、以下のようにRodauthインスタンスで呼び出されるブロックを渡せます。

# config/routes.rb
Rails.application.routes.draw do
  # 多要素認証のセットアップを要求する
  constraints Rodauth::Rails.authenticated { |rodauth| rodauth.uses_two_factor_authentication? } do
    # ...
  end
end

現在のアカウントは#rails_accountメソッドで取得できます。

# config/routes.rb
Rails.application.routes.draw do
  # adminユーザーであることを要求する
  constraints Rodauth::Rails.authenticated { |rodauth| rodauth.rails_account.admin? } do
    # ...
  end
end

設定名を渡すことでRodauth設定を指定できます。

# config/routes.rb
Rails.application.routes.draw do
  constraints Rodauth::Rails.authenticated(:admin) do
    # ...
  end
end

さらにカスタマイズが必要な場合は、いつでもルーティング制約を手動で作成できます。

# config/routes.rb
Rails.application.routes.draw do
  constraints -> (r) { !r.env["rodauth"].logged_in? } do # or "rodauth.admin"
    # ユーザーがログインしていないときのルーティング
  end
end

🔗 ビュー

Rodauthに組み込まれているビューテンプレートは使い始めのうちは便利ですが、そのうちマークアップを編集したくなるでしょう。以下のコマンドを実行すればRodauthのビューテンプレートをRailsアプリにコピーできます。

$ rails generate rodauth:views

メインの設定ファイルでRodauthControllerが設定されていれば、上のコマンドによって現在有効なRodauth機能のビューテンプレートがapp/views/rodauth/ディレクトリに生成されます。

Rodauthで利用したい機能のリストをジェネレータに渡せば、それらの機能が使えるビューを作成できます(既存のビューは削除されません)。

$ rails generate rodauth:views login create_account lockout otp

以下を実行すれば、すべての機能を含むビューを生成できます。

$ rails generate rodauth:views --all

別のRodauth設定で用いるビューを生成するには--nameオプションを指定します。

$ rails generate rodauth:views webauthn --name admin
🔗 ページタイトル

生成した設定ファイルにはtitle_instance_variableが設定され、ビューの@page_titleインスタンス変数でページタイトルにアクセス可能になります。これはレイアウトでも利用できます。

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    # ...
    title_instance_variable :@page_title
    # ...
  end
end
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
  <head>
    <title><%= @page_title || "Default title" %></title>
    <!-- ... -->
  </head>
  <body>
    <!-- ... -->
  </body>
</html>

ページタイトルを既にcontent_forで設定してある場合は、生成されたRodauthビューで対応する*_page_titleメソッドの結果を渡せるようになります。

<!-- app/views/rodauth/login.html.erb -->
<%= content_for :page_title, rodauth.login_page_title %>
<!-- ... -->
<!-- app/views/rodauth/change_password.html.erb -->
<%= content_for :page_title, rodauth.change_password_page_title %>
<!-- ... -->
🔗 レイアウト

Rodauthのビューごとに異なるレイアウトを使い分けたい場合は、layoutメソッドでリクエストパスを比較できます。

# app/controllers/rodauth_controller.rb
class RodauthController < ApplicationController
  layout :rodauth_layout

  private

  def rodauth_layout
    case request.path
    when rodauth.login_path,
         rodauth.create_account_path,
         rodauth.verify_account_path,
         rodauth.verify_account_resend_path,
         rodauth.reset_password_path,
         rodauth.reset_password_request_path
      "authentication"
    else
      "dashboard"
    end
  end
end
🔗 Turbo

Turboはデフォルトですべての組み込みおよび生成ビューテンプレートで無効になっています。理由は、一部のRodauth操作(multi-phaseログイン、リカバリーコードの追加)がTurboと互換性がなく、POSTリクエストで200レスポンスを返すためです。

しかしRodauthのほとんどはTurboと互換性があるので、Turboを使いたい操作では自由に有効にしてください。

🔗 メーラー

インストールジェネレータは、メールテンプレートを持つRodauthMailerをデフォルトで作成し、認証フローの一環としてメールを送信するRodauthの機能を利用する設定を行います。

# app/mailers/rodauth_mailer.rb
class RodauthMailer < ApplicationMailer
  def verify_account(account_id, key)
    # ...
  end
  def reset_password(account_id, key)
    # ...
  end
  def verify_login_change(account_id, old_login, new_login, key)
    # ...
  end
  def password_changed(account_id)
    # ...
  end
  # def email_auth(account_id, key)
  # ...
  # end
  # def unlock_account(account_id, key)
  # ...
  # end
end
# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    # ...
    create_reset_password_email do
      RodauthMailer.reset_password(account_id, reset_password_key_value)
    end
    create_verify_account_email do
      RodauthMailer.verify_account(account_id, verify_account_key_value)
    end
    create_verify_login_change_email do |_login|
      RodauthMailer.verify_login_change(account_id, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_key_value)
    end
    create_password_changed_email do
      RodauthMailer.password_changed(account_id)
    end
    # create_email_auth_email do
    #   RodauthMailer.email_auth(account_id, email_auth_key_value)
    # end
    # create_unlock_account_email do
    #   RodauthMailer.unlock_account(account_id, unlock_account_key_value)
    # end
    send_email do |email|
      # queue email delivery on the mailer after the transaction commits
      db.after_commit { email.deliver_later }
    end
    # ...
  end
end

この設定が呼び出す#deliver_laterは、Active Jobを用いてメールをバックグラウンドジョブで配信します。一般に、メールは非同期で送信することが推奨されます(リクエストのスループット向上および配信をリトライ可能にするため)。しかしメールを同期的に送信したい場合は、代わりに設定を変更して#deliver_nowを呼び出せます。

Active Jobアダプタを使わないバックグラウンド処理ライブラリを使う場合や、サードパーティのトランザクショナルなメール送信サービスを使う場合は、このWikiページでセットアップ方法を参照してください。

🔗 マイグレーション

インストールジェネレータは、デフォルトで有効になっているRodauth機能が利用するテーブルのマイグレーションを作成します。Rodauthの追加機能については、対応するテーブルをマイグレーションジェネレータで作成できます。

$ rails generate rodauth:migration otp sms_codes recovery_codes
# db/migration/*_create_rodauth_otp_sms_codes_recovery_codes.rb
class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
  def change
    create_table :account_otp_keys do |t| ... end
    create_table :account_sms_codes do |t| ... end
    create_table :account_recovery_codes do |t| ... end
  end
end
🔗 マイグレーション名をカスタマイズする

デフォルトのマイグレーション名は以下のオプションでカスタマイズできます。

$ rails generate rodauth:migration email_auth --name create_account_email_auth_keys
# db/migration/*_create_account_email_auth_keys
class CreateAccountEmailAuthKeys < ActiveRecord::Migration
  def change
    create_table :account_email_auth_keys do |t| ... end
  end
end

🔗 モデル

rodauth-model gemは、アカウントモデルにincludeできるRodauth::Modelミックスインを提供します。このミックスインは、password属性と、有効な認証機能で使われるテーブルの関連付けを定義します。

class Account < ActiveRecord::Base # Sequel::Model
  include Rodauth::Rails.model # または`Rodauth::Rails.model(:admin)`など
end

このpassword属性は、パスワードハッシュの設定や削除に利用できます。パスワードハッシュは、accountsテーブルのカラムに保存することも、別テーブルに保存することもできます。

account = Account.create!(email: "user@example.com", password: "secret123")

# パスワードハッシュをaccountsテーブルのカラムに保存する場合
account.password_hash #=> "$2a$12$k/Ub1I2iomi84RacqY89Hu4.M0vK7klRnRtzorDyvOkVI.hKhkNw."

# パスワードハッシュを別テーブルに保存する場合
account.password_hash #=> #<Account::PasswordHash...> (record from `account_password_hashes` table)
account.password_hash.password_hash #=> "$2a$12$k/Ub1..." (inaccessible when using database authentication functions)

account.password = nil # パスワードハッシュをクリアする
account.password_hash #=> nil

この関連付けは、有効な認証機能が利用するテーブルに対して定義されます。

account.remember_key #=> #<Account::RememberKey> (record from `account_remember_keys` table)
account.active_session_keys #=> [#<Account::ActiveSessionKey>,...] (records from `account_active_session_keys` table)

詳しくはrodauth-model gemのドキュメントを参照してください。

🔗 複数の設定を使い分ける

複数のアカウント種別ごとに異なる認証ロジックを使い分ける必要がある場合は、そのための新しい設定を作成できます。これは、Rodauth::Rails::Authのサブクラスを新たに作成し、名前をつけて登録することで行なえます。

# app/misc/rodauth_app.rb
class RodauthApp < Rodauth::Rails::App
  configure RodauthMain          # プライマリ設定
  configure RodauthAdmin, :admin # セカンダリ設定

  route do |r|
    r.rodauth         # プライマリRodauthリクエストへのルーティング
    r.rodauth(:admin) # セカンダリRodauthリクエストへのルーティング
  end
end
# app/misc/rodauth_admin.rb
class RodauthAdmin < Rodauth::Rails::Auth
  configure do
    # ... 機能を有効にする ...
    prefix "/admin"
    session_key_prefix "admin_"
    remember_cookie_key "_admin_remember" # パスワード保存機能を使う場合

    # `app/views/admin/rodauth`ディレクトリのビューを探索する
    rails_controller { Admin::RodauthController }
  end
end
# app/controllers/admin/rodauth_controller.rb
class Admin::RodauthController < ApplicationController
end

これで、アプリケーション内でセカンダリのRodauthインスタンスを参照できるようになります。

rodauth(:admin).login_path #=> "/admin/login"

どのアカウントがどの設定に属しているかという情報もデータベースに保存したいことがよくあります。詳しくはこのガイドを参照してください。

🔗 設定を共有する

Rodauthの複数の設定に含まれている共通設定を共有したい場合は、以下のように継承で共有できます。

# app/misc/rodauth_base.rb
class RodauthBase < Rodauth::Rails::Auth
  # 複数の設定で共有される共通設定
  configure do
    enable :login, :logout
    login_return_to_requested_location? true
    logout_redirect "/"
    # ...
  end
end
# app/misc/rodauth_main.rb
class RodauthMain < RodauthBase # 共通設定を継承
  configure do
    # ... mainをカスタマイズ ...
  end
end
# app/misc/rodauth_admin.rb
class RodauthAdmin < RodauthBase # 共通設定を継承
  configure do
    # ... adminをカスタマイズ ...
  end
end

🔗 リクエスト外部での利用

🔗 アクションを呼び出す

Rodauthをよりプログラム的に使う必要が生じることがあるかもしれません。認証操作をリクエストのコンテキストの外で行いたい場合は、そのためのinternal_request機能を利用できます。

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    enable :internal_request
  end
end
# プライマリ設定
RodauthApp.rodauth.create_account(login: "user@example.com", password: "secret123")
RodauthApp.rodauth.verify_account(account_login: "user@example.com")

# セカンダリ設定
RodauthApp.rodauth(:admin).close_account(account_login: "user@example.com")

🔗 URLを生成する

リクエストの外で認証用URLを生成するには、path_class_methodsプラグインを使います。

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    enable :path_class_methods
    create_account_route "register"
  end
end
# プライマリ設定
RodauthApp.rodauth.create_account_path # => "/register"
RodauthApp.rodauth.verify_account_url(key: "abc123") #=> "https://example.com/verify-account?key=abc123"

# セカンダリ設定
RodauthApp.rodauth(:admin).close_account_path(foo: "bar") #=> "/admin/close-account?foo=bar"

🔗 インスタンスメソッドを呼び出す

内部リクエストとして公開されていないRodauthメソッドにアクセスする必要がある場合は、Rodauth::Rails.rodauthを利用すればinternal_request機能で使われるRodauthインスタンスを取得できます。

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    enable :internal_request # これは必須
  end
end
account = Account.find_by!(email: "user@example.com")
rodauth = Rodauth::Rails.rodauth(account: account) #=> #<RodauthMain::InternalRequest ...>

rodauth.compute_hmac("token") #=> "TpEJTKfKwqYvIDKWsuZhkhKlhaBXtR1aodskBAflD8U"
rodauth.open_account? #=> true
rodauth.two_factor_authentication_setup? #=> true
rodauth.password_meets_requirements?("foo") #=> false
rodauth.locked_out? #=> false

Rodauth::Rails.rodauthメソッドは、:accountオプションの他に、internal_request機能でサポートされている任意のオプションを受け取れます。

# プライマリ設定
Rodauth::Rails.rodauth(env: { "HTTP_USER_AGENT" => "programmatic" })
Rodauth::Rails.rodauth(session: { two_factor_auth_setup: true })

# セカンダリ設定
Rodauth::Rails.rodauth(:admin, params: { "param" => "value" })

🔗 テスト

ミドルウェアスタック全体を実行するシステムテストや統合テストでは、HTTPエンドポイントで通常通り認証をテストできます。このWikiページでいくつかのサンプルを参照できます。

コントローラのテストでは、セッションを変更する形でアカウントにログインできます。

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action -> { rodauth.require_account }

  def index
    # ...
  end
end
# test/controllers/articles_controller_test.rb
class ArticlesControllerTest < ActionController::TestCase
  test "required authentication" do
    get :index

    assert_response 302
    assert_redirected_to "/login"
    assert_equal "Please login to continue", flash[:alert]

    account = Account.create!(email: "user@example.com", password: "secret123", status: "verified")
    login(account)

    get :index
    assert_response 200

    logout

    get :index
    assert_response 302
    assert_equal "Please login to continue", flash[:alert]
  end

  private

  # セッションを手動で変更してRodauthが期待する形にする
  def login(account)
    session[:account_id] = account.id
    session[:authenticated_by] = ["password"] # MFAの場合は["password", "totp"]
  end

  def logout
    session.clear
  end
end

異なるセッションプレフィックスごとに複数の設定を使い分ける場合は、コントローラのテストでもプレフィックスの使い分けが必要です。

class RodauthAdmin < Rodauth::Rails::Auth
  configure do
    session_key_prefix "admin_"
  end
end
# コントローラのテスト
session[:admin_account_id] = account.id
session[:admin_authenticated_by] = ["password"]

コントローラのテストでRodauthのインスタンスにアクセスしたい場合は、以下のようにコントローラのインスタンスを介してアクセスします。

# コントローラのテスト
@controller.rodauth         #=> #<RodauthMain ...>
@controller.rodauth(:admin) #=> #<RodauthAdmin ...>

🔗 設定方法

🔗 設定用メソッド

rodauth-railsが読み込むrails機能では、以下の設定メソッドが提供されます。

メソッド名 説明
rails_render(**options) 指定のレンダリングオプションでテンプレートをレンダリングする
rails_csrf_tag CSRFトークンを含む隠しフィールドをRodauthテンプレートに追加する
rails_csrf_param CSRFタグのname属性の値
rails_csrf_token CSRFタグのvalue属性の値
rails_check_csrf! 現在のリクエストで認証トークンを検証する
rails_controller_instance リクエストのenvコンテキストを持つコントローラのインスタンス
rails_controller レンダリングやCSRF保護に用いるコントローラのクラス
rails_account_model accountsテーブルに接続するモデルのクラス

Rodauthが提供する設定メソッドのリストについては、Rodauthの機能ドキュメントを参照してください。

🔗 カスタムメソッドを定義する

Rodauthのすべての設定メソッドは、認証クラスでインスタンスメソッドを定義するための単なるシンタックスシュガーです。以下のように認証クラスでカスタムメソッドを独自に定義することも可能です。

class RodauthMain < Rodauth::Rails::Auth
  configure do
    # ...
    password_match? { |password| ldap_valid?(password) }
    # ...
  end

  # 外部のidentitiesテーブルの例
  def identities
    db[:account_identities].where(account_id: account_id).all
  end

  private

  # LDAP認証の例
  def ldap_valid?(password)
    SimpleLdapAuthenticator.valid?(account[:email], password)
  end
end
rodauth.identities #=> [{ provider: "facebook", uid: "abc123", ... }, ...]

🔗 RailsのURLヘルパー

Rodauth設定やrouteブロックの中では、以下のように#rails_routes経由でRailsのルーティングヘルパーにアクセスできます。

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    login_redirect { rails_routes.activity_path }
  end
end

🔗 コントローラのメソッドを呼び出す

Rodauthのbefore/afterフックを使うときやRodauth設定をオーバーライドするときに、コントローラで定義されたメソッドを呼び出したいことがよくあります。たとえばrails_controller_evalメソッドを使うと以下のようなことができます。

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  private
  def setup_tracking(account_id)
    # ... 何か実装する ...
  end
end
# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    after_create_account do
      rails_controller_eval { setup_tracking(account_id) }
    end
  end
end

🔗 単一の設定ファイルにまとめる

Rodauthのすべてのロジックを1個のファイルにまとめたい場合は、Rodauth::Rails::App.configureにブロックを渡して呼び出すことで匿名の認証クラスが作成されます。

# app/misc/rodauth_app.rb
class RodauthApp < Rodauth::Rails::App
  # プライマリ設定
  configure do
    enable :login, :logout, :create_account, :verify_account
    # ...
  end

  # セカンダリ設定
  configure(:admin) do
    enable :email_auth, :single_session
    # ...
  end

  route do |r|
    # ...
  end
end

🔗 rodauth-railsのしくみ

🔗 Rackミドルウェア

RailsのrailtieはRackミドルウェアスタックの末尾にRodauth::Rails::Middlewareを挿入し、これがリクエストごとにRodauthアプリを呼び出します。

$ rails middleware
# ...
# use Rodauth::Rails::Middleware
# run MyApp::Application.routes

このミドルウェアは、Rackミドルウェアスタックの任意の場所に挿入できます。

Rodauth::Rails.configure do |config|
  config.middleware = false # ミドルウェアの自動挿入を無効にする
end

Rails.application.config.middleware.insert_before AnotherMiddleware, Rodauth::Rails::Middleware

このミドルウェアは、Rodauth::Rails.appを介してRodauthアプリを取得します。このクラスをdevelopmentモードでオートロード可能・リロード可能にするために、クラス名を文字列で指定します。

Rodauth::Rails.configure do |config|
  config.app = "RodauthApp"
end

これによってZeitwerkとの互換性が保たれるとともに、コントローラレベルで発生するRodauthのリダイレクト(例: before_actionフィルタでrodauth.require_accountを呼ぶ)をこの追加の層がキャッチするようになります。

🔗 Rodaアプリ

Rodauth::Rails::AppクラスはRodaのサブクラスで、以下を行う有用な層をRodauthに提供します。

  • Action DispatchのFlashメッセージを利用する
  • Rodauthプラグイン読み込み用のシンタックスシュガーを提供する
  • RodauthオブジェクトをRackのenvハッシュに保存する
  • 編集されたヘッダーをRailsのレスポンスに展開する
🔗 ブロックを渡して設定する

configure呼び出しはRodauthプラグインを読み込みます。規約としては、この呼び出しは認証クラスと設定名(それぞれプラグインの:auth_classオプションと:nameオプションに転送されます)を位置引数として受け取ります。また、匿名の認証クラス用のブロックを受け取り、プラグインの任意の追加オプションも受け取ります。

class RodauthApp < Rodauth::Rails::App
  # 名前を持つ認証クラス
  configure(RodauthMain)
  configure(RodauthAdmin, :admin)

  # 匿名の認証クラス
  configure { ... }
  configure(:admin) { ... }

  # プラグインのオプション
  configure(RodauthMain, json: :only)
end
🔗 ブロックを渡してルーティングする

routeに渡したブロックは、Railsルーターに到達する前にリクエストごとに呼び出されてリクエストオブジェクトをyieldします。

class RodauthApp < Rodauth::Rails::App
  route do |r|
    # 各リクエストの前に呼び出される
  end
end
🔗 ルーティングのprefix

ルーティングでprefixを指定すると、r.rodauthは自動的にプレフィックスにルーティングされるように変更されるので、素のRodauthのようなr.on呼び出しの追加が不要になります。

class RodauthApp < Rodauth::Rails::App
  configure do
    prefix "/user"
  end

  route do |r|
    r.rodauth # `r.on("user") { ... }`で囲む必要はない
  end
end

🔗 Authクラス

Rodauth::Rails::AuthクラスはRodauth::Authのサブクラスで、Rodauthのrails機能をプリロードしてHMACのsecretをRailsのsecretキーベースに設定し、いくつかのデフォルト設定を変更します。

class RodauthMain < Rodauth::Rails::Auth
  configure do
    # 認証の設定
  end
end

🔗 Rodauthの機能

Rodauth::Rails::Authで読み込まれるRodauthのrails機能は、RodauthをRailsに統合するうえで主要な部分を提供します。

  • テンプレートのレンダリングにAction Viewを利用する
  • CSRF保護にAction Dispatchを利用する
  • Action Controllerコールバックと、Rodauthリクエストの前後のブロックからのrescueを実行する
  • メール作成と配信にAction Mailerを利用する
  • Rodauthリクエストの前後でAction Controllerのinstrumentationを利用する
  • リクエストの外でRodauthを呼ぶときにAction MailerのデフォルトURLオプションを利用する

🔗 コントローラ

Rodauthアプリは、Rodauth::Rails::AuthのインスタンスをRackのenvハッシュに保存し、Railsアプリで利用できるようにします。

request.env["rodauth"]       #=> #<RodauthMain>
request.env["rodauth.admin"] #=> #<RodauthAdmin> (設定を複数使う場合)

利便性のため、ビューやコントローラでも#rodauthメソッドでこのオブジェクトにアクセスできるようになっています。

class MyController < ApplicationController
  def my_action
    rodauth         #=> #<RodauthMain>
    rodauth(:admin) #=> #<RodauthAdmin> (設定を複数使う場合)
  end
end
<% rodauth         #=> #<RodauthMain> %>
<% rodauth(:admin) #=> #<RodauthAdmin> (設定を複数使う場合) %>

🔗 Sequelについて

RodauthはデータベースとのやりとりにSequelライブラリを利用しています。Sequelは高度なクエリを構築できる強力なAPIを提供します(SQL式、データベース非依存の日付演算、SQL関数呼び出しをサポートします)。

アプリケーションでActive Recordを使う場合は、rodauth:installジェネレータが自動的にSequelがActive Recordのデータベースコネクションを再利用するように、sequel-activerecord_connection gemを利用してSequelを設定します。

つまり、利用上はSequelをRodauth実装の単なる詳細部分とみなせます。

🔗 Rodauthのデフォルト設定

rodauth-railsは、セットアップを容易にするためにRodauth設定の一部を変更します。

🔗 データベース関数

PostgreSQL、MySQL、Microsoft SQL Serverの場合、デフォルトのRodauthはパスワードハッシュにアクセスするときにデータベース関数を利用します。つまり、アプリケーションを実行するユーザーがパスワードハッシュに直接アクセスすることは不可能になります。これにより、攻撃者がパスワードハッシュにアクセスして他のサイトの攻撃に転用するリスクを軽減します。

この機能は追加のセキュリティとして有用ですが、データベースのユーザーが2つ必要で、適切なデータベースユーザーに対してマイグレーションを実行しなければならなくなるため、その分セットアップが複雑になり、理解も難しくなります。

Railsの「設定より規約」という教義を守るために、rodauth-railsではRodauthのこのデータベース関数の利用を無効にしてありますが、以下の設定でいつでも有効にできます。

use_database_authentication_functions? true

データベース関数を作成するには、RodauthのメソッドにSequelのデータベースオブジェクトを渡してください。

# db/migrate/*_create_rodauth_database_functions.rb
require "rodauth/migrations"

class CreateRodauthDatabaseFunctions < ActiveRecord::Migration
  def up
    Rodauth.create_database_authentication_functions(DB)
  end

  def down
    Rodauth.drop_database_authentication_functions(DB)
  end
end

🔗 アカウントのステータス

推奨されているRodauthのマイグレーションでは、可能なアカウントのステータス値を別テーブルに保存し、accountsテーブルに外部キーを作成して、有効なステータス値だけが永続化されるようにしています。

しかし残念ながら、スキーマファイルからデータベースをリストアした場合はこのようにならず、アカウントステータスのテーブルが空になります。testモードではデフォルトでこれが発生しますが、developmentモードで発生することも珍しくありません。

この問題に対処するため、rodauth-railsはstatusカラムを別テーブルに分けずに利用します。無効なステータス値が混入することが心配な場合は、代わりにenumを使うとよいでしょう。また、Rodauthの推奨セットアップに戻すことはいつでも可能です。

# マイグレーション
create_table :account_statuses do |t|
  t.string :name, null: false, unique: true
end
execute "INSERT INTO account_statuses (id, name) VALUES (1, 'Unverified'), (2, 'Verified'), (3, 'Closed')"

create_table :accounts do |t|
  # ...
  t.references :status, foreign_key: { to_table: :account_statuses }, null: false, default: 1
  # ...
end
  class RodauthMain < Rodauth::Rails::Auth
    configure do
      # ...
-     account_status_column :status
      # ...
    end
  end

🔗 deadline値

データベーススキーマを変更しやすくするため、rodauth-railsはRodauthが「カラムのデフォルト値設定をデータベースに依存する」のではなく「Rubyのさまざまな機能で用いられるdeadline値を設定する」形でRodauthの設定を変更します。

この設定は簡単に無効にできます。

set_deadline_values? false

ライセンス

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the rodauth-rails project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

関連記事

Rails: 認証gem ‘Rodauth’を統合するrodauth-railsを開発しました(翻訳)

Ruby: 認証gem「Rodauth」README(翻訳)

The post Rails: 認証gem ‘rodauth-rails’ README(翻訳) first appeared on TechRacho.


Viewing all articles
Browse latest Browse all 1765

Trending Articles