Sorceryメモ

認証機能を追加するGem。Sorcery 0.16.0 で確認。

以下のgemを追加する。

gem 'sorcery'

bin/rails g sorcery:installで以下のファイルが作成される。

app/models/user.rb
config/initializers/sorcery.rb
db/migrate/20210310200552_sorcery_core.rb
spec/factories/users.rb
spec/models/user_spec.rb

マイグレーションファイルはusersテーブルを作成する。デフォルトのテーブル名がusersではなくUserになってしまうバグがあるので、create_tableadd_index:User:usersに直す必要がある。マージはされているので、Sorcery 0.16.1以上では解消されるはず。

class SorceryCore < ActiveRecord::Migration[6.1]
  def change
    create_table :User do |t|
      t.string :email,            null: false
      t.string :crypted_password
      t.string :salt

      t.timestamps                null: false
    end

    add_index :User, :email, unique: true
  end
end

サブモジュールを追加したい場合はbin/rails g sorcery:install remember_me --only-submodulesのコマンドを実行する。bin/rails g sorcery:installを実行する前だったらbin/rails g sorcery:install remember_meのようにすれば一緒に追加される。

モデルをUser以外にしたい場合はbin/rails g sorcery:install --model Accountのようにする。

Core機能

require_login

before_action :require_loginの形で使い、ログインしていない場合はroot_pathにリダイレクトさせる。リダイレクト先を変えたい場合はnot_authenticatedメソッドをオーバーライドする。

ログイン画面などログインしていないことが前提の画面はskip_before_actionを設定しておく。

class ApplicationController < ActionController::Base
  before_action :require_login

  private

  # https://github.com/Sorcery/sorcery/blob/43efa3329e61d3fff8a5233d9efdc90db6820362/lib/sorcery/controller.rb#L108
  # 以下はデフォルトと同じ
  def not_authenticated
    redirect_to root_path
  end
end

class UserSessionsController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]
end

login

認証処理を行うメソッド。login(メールアドレス, パスワード)の形で使う。認証に成功すればそのUserモデルのインスタンス、失敗すればnilが返る。sessionにも自動的に情報が追加される。

class UserSessionsController < ApplicationController
  def create
    @user = login(params[:email], params[:password])

    if @user
      redirect_back_or_to(:users, notice: 'Login successful')
    else
      flash.now[:alert] = 'Login failed'
      render action: 'new'
    end
  end
end

logout

ログアウトを行うメソッド。

class UserSessionsController < ApplicationController
  def destroy
    logout
    redirect_to(:users, notice: 'Logged out!')
  end
end

auto_login

引数で指定したユーザーにログインする。引数はModelのインスタンスでメールアドレスではない。

auto_login(User.first)

logged_in?

ログインしているかどうか。ログインしていればtrueを返す。

current_user

ログイン中のユーザーを返す。

redirect_back_or_to

「ログアウト状態でログインが必要な画面にアクセス=>ログイン画面にリダイレクト=>ログイン=>表示しようとしていた画面を表示する」という流れを実現するメソッド。

redirect_back_or_to(default_url, flash_messages)という形で実行する。もし、ログアウト状態でログインが必要な画面にアクセスせずにログインした場合は第一引数で指定したURLにリダイレクトする。redirect_toメソッドに渡しているのでredirect_toメソッドに指定できる形ならどのような形でも良い。flash_messagesも同様でredirect_toメソッドのflash引数に指定しているだけである。

User#external?

FacebookやTwitterなど外部認証を利用していればtrueを返す。実際のところは、users.crypted_passwordカラムが空でないかチェックしている。

User#active_for_authentication?

ユーザーの凍結機能などを実現したい場合に定義する。このメソッドがfalseを返すとそのユーザーはログインができない。デフォルトでは未定義のメソッドで定義すると呼ばれるようになる。

class User < ApplicationRecord
  authenticates_with_sorcery!

  def active_for_authentication?
    !account_frozen?
  end
end

User#valid_password?

引数のパスワードが正しいパスワードならtrueを返す。User.first.valid_password?('password')

User.authenticates_with_sorcery!

Sorceryの機能をUserモデルに読み込む。

class User < ApplicationRecord
  authenticates_with_sorcery!
end

HTTP Basic Auth機能

BASIC認証でログイン機能を提供するサブモジュール。

HTTP Basic Auth機能を利用するにはbin/rails g sorcery:install http_basic_auth --only-submodulesを実行してサブモジュールを追加する必要がある。

config/initializers/sorcery.rbファイルにハッシュを指定するconfig.controller_to_realm_mapオプションがある。WWW-Authenticateヘッダに指定される情報で、BASIC認証のダイアログにメッセージとして表示される。

# config/initializers/sorcery.rb
config.controller_to_realm_map = {
  'スネークケースのコントローラー名' => 'メッセージ',
  'user_sessions' => 'Please enter your email and password.'
}

デフォルトでは、{"application" => "Application"}が指定されていてすべてのコントローラーでApplicationというメッセージが表示される。

以下のようにbefore_actionを指定するとログイン画面代わりにBASIC認証が使えるようになる。アカウントはUserテーブルのものと照合される。

before_action :require_login_from_http_basic

Remember Me機能

ブラウザを閉じてもログインを継続させるサブモジュール。

Remember Me機能を利用するにはbin/rails g sorcery:install remember_me --only-submodulesを実行してサブモジュールを追加する必要がある。また、マイグレーションファイルが作成されるので、bin/rails db:migrateを実行しておく。

設定

user.remember_me_forに何日間Remember Meを有効にするか指定する。デフォルトでは60 * 60 * 24 * 7が指定されている。

# config/initializers/sorcery.rb
user.remember_me_for = 2.weeks

user.remember_me_token_persist_globallyに複数ブラウザでRemember Meを有効にするか指定する。falseだとusers.remember_me_tokenがログインするたびに変更されるがtrueだと変更されなくなる。結果として、同じトークンが複数ブラウザで共有されることになる。デフォルトはfalse

# config/initializers/sorcery.rb
user.remember_me_token_persist_globally = true

login

Remember Me機能を有効にするには、login(email, password, true)のようにloginメソッドの第3引数にtrueを指定する。画面上にチェックボックスを用意してユーザーに選択させる方法もある。

auto_login

Remember Me機能を有効にするには、auto_login(User.first, true)のようにloginメソッドの第3引数にtrueを指定する。画面上にチェックボックスを用意してユーザーに選択させる方法もある。

remember_me!

トークンと有効期限をリセットするメソッド。ログイン中の時に使わなければならない。

forget_me!

トークンと有効期限を削除するメソッド。user.remember_me_token_persist_globallytrueだと削除しない。

force_forget_me!

トークンと有効期限を削除するメソッド。forget_me!と違って常に削除される。

モデルのメソッドについて

いくつかモデルに呼び出せるメソッドが存在するが、通常はコントローラーのメソッド以外は呼び出す必要がないはずである。

Reset Password機能

パスワードを忘れた場合に利用するリセット機能を提供するサブモジュール。

Reset Password機能を利用するにはbin/rails g sorcery:install reset_password --only-submodulesを実行してサブモジュールを追加する必要がある。また、マイグレーションファイルが作成されるので、bin/rails db:migrateを実行しておく。

bin/rails g mailer UserMailer reset_password_emailを実行し、メーラーを追加する。メーラーのクラス名もメソッドも設定で指定できるのでなんでも良い。

基本的な使い方は以下の通り。

  • メールアドレスを入力するリセット画面を用意(newアクション)
  • メールアドレスでユーザーを特定しUser#deliver_reset_password_instructions!メソッドを呼び出す(createアクション)
  • メーラーにusers.reset_password_tokenカラムの内容つきのeditアクションへのURLを埋め込む
  • User.load_from_reset_password_token(トークン)を呼び出してユーザーを特定し、新しいパスワードを入力する画面を用意(editアクション)
  • User.load_from_reset_password_token(トークン)を呼び出してユーザーを特定し、入力されたパスワードでUser#change_passwordを呼び出す(updateアクション)

詳細はReset password · Sorcery/sorcery Wiki · GitHubを参照。

設定

user.reset_password_mailerにメーラーのクラスを指定する。デフォルトはnilなので必ず指定する必要がある。user.reset_password_email_method_nameにメーラーのメソッドを指定する。デフォルトは:reset_password_email

user.reset_password_mailer = UserMailer
user.reset_password_email_method_name = :reset_password_email

user.reset_password_mailer_disabledtrueを設定するとUser#generate_reset_password_token!を読んだ時に自動的なメール送信をしなくなる。デフォルトはfalse

user.reset_password_mailer_disabled = false

user.reset_password_expiration_periodにトークンが有効な時間を指定する。デフォルトはnilで無期限。

user.reset_password_expiration_period = 1.day

user.reset_password_time_between_emailsに前回の送信から再送信可能までの時間を指定する。デフォルトは5 * 60

user.reset_password_time_between_emails = 5.minutes

user.email_delivery_methodにメーラーのデリバリメソッドを指定する。:deliver_later:deliver_nowが指定可能(Rails 4.2より前なら:deliver)。デフォルトは:deliver_now。Reset Password機能、User Activation機能、Brute force protection機能で共通の設定。

user.email_delivery_method = :deliver_now

User#deliver_reset_password_instructions!

リセットトークンなどの情報をUserテーブルに保存して、パスワードリセットのメールを送信する。

User#generate_reset_password_token!

リセットトークンなどの情報をUserテーブルに保存する。User#deliver_reset_password_instructions!メソッドの中から呼び出されている。

User.load_from_reset_password_token

引数にパスワードリセットトークンを指定してユーザーを取得する。User.load_from_reset_password_token(User.first.reset_password_token)という感じ。

User#change_password

パスワードを設定してリセットトークンなどの情報を無効化する。User.first.change_password('password')という感じ。User#change_password!というメソッドもあり、User#change_passwordsaveメソッドで、User#change_password!save!メソッドで保存しようとする。

User#increment_password_reset_page_access_counter

users.access_count_to_reset_password_pageカラムをインクリメントする。このカラムは少なくとも現行バージョン(2021/3/13 Sorcery 0.16.0)ではどこでも使われていないカラムである。

名前の通り、パスワードリセット画面にアクセスした回数を記録してN回までアクセス可能というような制御を想定している。しかし、想定しているだけでSorceryとしては何も機能を提供していないので、そういった制御をしたいなら自分で面倒を見る必要がある。

また、User#generate_reset_password_token!を読んだところでカウントがリセットされるわけでもないのでUser#reset_password_reset_page_access_counterメソッドを読んでリセットする必要がある。

Session Timeout機能

指定した時間でセッションを無効にする機能。

Session Timeout機能を利用するにはbin/rails g sorcery:install session_timeout --only-submodulesを実行してサブモジュールを追加する必要がある。

設定

config.session_timeoutにセッションが有効な時間を秒数で指定する。デフォルトは3600

config.session_timeout_from_last_actiontrueを指定するとアクセスのたびにセッションタイムアウトの時間がリセットされる(falseならログイン時点からconfig.session_timeout秒経過したらセッションタイムアウトする)。デフォルトはfalse

config.session_timeout_invalidate_active_sessions_enabledtrueを設定し、Userモデルにinvalidate_sessions_beforeタイムスタンプカラムを追加するとinvalidate_active_sessions!メソッドが使えるようになる。強制的なセッションの無効化をする。

# config/initializers/sorcery.rb
config.session_timeout = 3.hours
config.session_timeout_from_last_action = true
config.session_timeout_invalidate_active_sessions_enabled = true

invalidate_active_sessions!

このメソッドを使うにはconfig.session_timeout_invalidate_active_sessions_enabledtrueを指定し、Userモデルにinvalidate_sessions_beforeタイムスタンプカラムを追加しておく必要がある。

このメソッドを使うとinvalidate_sessions_beforeカラムに現在時刻が保存され、invalidate_sessions_beforeカラムより前に有効になっているセッションを無効化する。つまり、すべてのブラウザから一律で強制ログアウトさせる機能。

User Activation機能

新規登録時に指定されたメールアドレスにメールを送って有効なメールアドレスが登録されているか確認する機能。コールバックを追加すればメールアドレスの変更時にも対応できる。

User Activation機能を利用するにはbin/rails g sorcery:install user_activation --only-submodulesを実行してサブモジュールを追加する必要がある。また、マイグレーションファイルが作成されるので、bin/rails db:migrateを実行しておく。

bin/rails g mailer UserMailer activation_needed_email activation_success_emailを実行し、メーラーを追加する。メーラーのクラス名もメソッドも設定で指定できるのでなんでも良い。

設定

user.user_activation_mailerにメーラーのクラスを指定する。デフォルトはnilなので必ず指定する必要がある。user.activation_needed_email_method_nameにメールアドレス確認用のメーラーのメソッドを指定する。デフォルトは:activation_needed_emailuser.activation_success_email_method_nameにメールアドレスの確認が成功した時用のメーラーのメソッドを指定する。デフォルトは:activation_success_email

user.user_activation_mailer = UserMailer
user.activation_needed_email_method_name = :activation_needed_email
user.activation_success_email_method_name = :activation_success_email

user.activation_token_expiration_periodにトークンが有効な時間を指定する。デフォルトはnilで無期限。

user.activation_token_expiration_period = 1.day

user.activation_mailer_disabledtrueを指定すると自動的なメールの送信を行わなくなる。デフォルトはfalse。truthyな値、falsyな値ではなく厳密にtruefalseで判定しているので注意。

user.activation_mailer_disabled = false

user.email_delivery_methodにメーラーのデリバリメソッドを指定する。:deliver_later:deliver_nowが指定可能(Rails 4.2より前なら:deliver)。デフォルトは:deliver_now。Reset Password機能、User Activation機能、Brute force protection機能で共通の設定。

user.email_delivery_method = :deliver_now

user.prevent_non_active_users_to_logintrueを指定するとアクティベーションを行っていないユーザーのログインをできないようにする。デフォルトはtrue

user.prevent_non_active_users_to_login = true

User.load_from_activation_token

引数にアクティベーショントークンを指定してユーザーを取得する。User.load_from_activation_token(User.first.activation_token)という感じ。

User#setup_activation

アクティベーショントークンなどの情報をUserモデルのインスタンスにセットアップする。保存はしないので、setup_activationメソッドを読んだ後にsaveメソッドを呼ぶ必要がある。

Userモデルの新規登録時はコールバックで自動的に呼ばれる。

User#activate!

アクティベーションが成功した状態にする。アクティベーションに成功したメールの送信も行う。

Brute force protection機能

設定

user.consecutive_login_retries_amount_limitには何回ログインに失敗したらアカウントをロックするか指定する。デフォルトは50

user.consecutive_login_retries_amount_limit = 50

user.login_lock_time_periodにはアカウントロック後に何秒間アカウントロックを継続するかを指定する。デフォルトは60 * 60

user.login_lock_time_period = 1.hour

user.unlock_token_mailerにはアカウントロックされたことを通知するメーラーのクラスを指定する。デフォルトはniluser.unlock_token_email_method_nameにはメーラーのメソッド名を指定する。デフォルトは:send_unlock_token_emailuser.unlock_token_mailer_disabledtrueを指定するとメールを送信しなくなる。デフォルトはfalse

user.unlock_token_mailer = UserMailer
user.unlock_token_email_method_name = :send_unlock_token_email
user.unlock_token_mailer_disabled = false

user.login_lock_time_period0にしてuser.unlock_token_mailerなどの設定をしないとロックの解除手段がなくなるので注意。

User#register_failed_login!

ログイン失敗を記録する。ログイン失敗回数のカウントアップと指定回数を超えた場合にアカウントロックを行うメソッド。ログイン失敗時に自動的に呼ばれるので、通常は意識してこのメソッドを呼び出すことはないはず。

User#login_unlock!

アカウントロック情報をリセットしてロックを解除する。user.login_lock_time_period1以上なら指定時間経過後のログインでこのメソッドが呼ばれる。ロック解除メールを飛ばす場合はコントローラーでこのメソッドを呼ぶ。

User#login_locked?

アカウントがロックされていればtrueを返す。

User.load_from_unlock_token

引数にロック解除トークンを指定してユーザーを取得する。User.load_from_unlock_token(User.first.unlock_token)という感じ。

Activity logging機能

ログイン時刻などを記録するサブモジュール。参照:Activity logging · Sorcery/sorcery Wiki · GitHub

External機能

Twitterなどによるログイン機能を提供するサブモジュール。参照:External · Sorcery/sorcery Wiki · GitHub

Magic login機能

簡単に言えばパスワード抜きでメールだけでログインができるようにする機能。

日本の人が作成した機能のようなので、そちらの記事を参照。【Rails × sorcery】Slackみたいなパスワードなしのメールだけログイン機能を実装してみる

なお、公式ドキュメントはなさそう。