sorceryって何??sorceryを使った、ユーザー登録、ログイン&ログアウト機能の実装からsorceryとはなんぞやを調べてみた。

sorceryとは?

sorceryとは、ユーザ認証機能を簡単に実装できるライブラリ(gem)です。同じように認証機能を提供してくれているものとしてdeviseなどが挙げられますが、sorceryの方がよりシンプルで、カスタマイズ性に富んでいるという特徴を持ちます。

ユーザの認証の基本的な機能であるパスワード認証を始め、

  • User Activation
  • Reset Password
  • Remember Me
  • Session Timeout
  • Brute Force Protection
  • Basic HTTP Authentication
  • Activity Logging

といった機能が揃っていて、必要な機能を選んで使うことができます。

間違えやすい点としては、Sorceryはログイン認証機能を実装する為のGemであり、ログイン認証機能自体はSorceryが提供するメソッドによって実現されます。 まとめると、SorceryというGemをRailsにインストールすると、Sorceryが提供する様々なログインに関するメソッドを使えるようになるということです。

[引用:【Rails】駆け出しVimmerがSorceryをざっくり説明してみた]

Sorceryをインストールして、ユーザー登録機能を実装する

RailsアプリケーションのGemfileにsorceryを追加します。

gem 'sorcery'

コマンドラインで、bundle installしコマンドを実行し、Gemfileに記載したSorceryGemをインストールします。

$ bundle install

3 **Sorceryによって追加されたジェネレーターを実行して、Sorcery Gemを使ったアプリの構築を開始します。

まず、Sorceryが提供するジェネレータbundle exec rails g sorcery:installを実行し、Userモデルとmigrationファイルを生成します。**

$ bundle exec rails g sorcery:install

bundle execって必要なの? - Qiita

上記のコマンド実行後、次のファイルが作成されます。↓

  • app/models/user.rb(userモデル)
  • config/initializers/sorcery.rb(initializersファイル)
  • db/migrate/yyyymmddhhmmss_sorcery_core.rb(migrationファイル)

4 rake rails db:migrateを実行して、usersテーブルを生成します。

$ rake db:migrate

5 以下のコマンドをターミナル上で実行して、Userモデルに対応したビューとコントローラを作成します。

同時に、userテーブルにstring型のemailカラムとstring型のcrypted_password(暗号化されたパスワード)カラムとstring型の※saltカラムを作成します。

$ rails g scaffold_controller user email:string crypted_password:string salt:string

仮想属性とは? 例えば今回のように、databaseのusersテーブルがあるとします。ユーザー情報を登録するusersテーブルにはpasswordというカラムはなく、password〇〇や、〇〇password といった暗号化されたパスワードを入れるようになっています。今回はcrypted_passwordカラムに暗号化されたパスワードが入るようになっています。 そして、crypted_passwordカラムにパスワードを暗号化して入れるためにpassword属性とpassword_confirmation属性という仮想属性を作っているのです。仮想属性にパスワードを入れてsaveすると、暗号化されたものが実際にcrypted_passwordカラムに入ります。 つまり、DBのテーブルにパスワードを保存するために仮想属性が必要になるということです。 実際には、DBに存在しない属性(カラム)であることから、仮想属性と呼ばれます。 params.permit(:password) するときに、password= というsetter methodがないとエラーになりますので atter_accessorを使って、getter、setterの仮属性定義が必要です。


password_confirmation(パスワードコンファーメイション)とは、データベースに保存されない仮想の属性で、パスワードの入力確認に使われます。

この属性を使用するとpasswordpassword_confirmationの双方が一致しているかのバリデーションが自動で追加されます。


現在パスワードの保管方法は、単にパスワードをハッシュ化して、暗号化したものを保管するのではなく、元のパスワードの文字列に別の文字列(ソルト)を付け加えることで文字列全体を長くしてから、繋げた文字列をまとめてハッシュ化したものをDBに格納しています。そうすることで、よりセキュリティを高めることができるのです。DBにはソルトとハッシュ値を保存しています。ハッシュ化(パスワード文字列+ソルト)=ハッシュ値

6 暗号化されたパスワードやソルトをユーザーに編集/閲覧させることはセキュリティ上、望ましくないため、ビューでこれらの属性を表示させないようにします。

ビューであるapp / views / users /のすべてのテンプレートからこれらの属性を削除します。

7 UsersControllerでStrong parameterを定義してフォームの特定の属性の値を取得できるように設定します。

class UsersController < ApplicationController
  # ...
  private

  def user_params
        # paramsオプジェクトとして受け取れる値は、userテーブルのemail属性、password属性、password_confirmation属性の値のみと設定。
        # ここで、password属性、password_confirmation属性の2つの属性は仮想属性であり、実際にはDBに存在しない属性です。passwordとpassword_confirmationの双方の値が一致しているか確認した後、暗号化されたものがDBに保存されます。
    params.require(:user).permit(:email, :password, :password_confirmation)
  end
end

8 暗号化されたパスワードがDBに保存される前に、ユーザーが入力したパスワードをフォームで受け取るために仮想フィールドをビュー(今回はビューテンプレート)に追加する必要があります。

# app/views/users/_form.html.erb
<div class="field">
   <%= form.label :password %><br />
   <%= form.password_field :password %>
</div>
<div class="field">
   <%= form.label :password_confirmation %><br />
   <%= form.password_field :password_confirmation %>
</div>

9 Userモデルに以下の記載をします。validates :password, confirmation: trueをUserモデルに設定することで、Userモデルにpassword属性とpassword_confirmation属性が定義されます。

(この2つはuserテーブルのcrypted_passwordカラムに紐づくUserモデルの仮想属性です。) この2つの仮想属性は暗号化されてから、userテーブルでcrypted_passwordカラムに変更されるようになっています。

# app/models/user.rb
class User < ActiveRecord::Base
  authenticates_with_sorcery!

  validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }

  validates :email, uniqueness: true
end

10 最後に、ルーティングを定義します。

# config/routes.rb
root :to => 'users#index'
resources :users

アプリを実行し、http://localhost:3000/usersにアクセスすると、ユーザー新規登録ページに遷移し、そこで新しいユーザーを作成できます。Soceryを使用しているため、パスワードは自動的に暗号化され、ソルトも自動作成されます。

Sorceryを使って、ログイン・ログアウト機能を実装する

1 . ユーザーのログイン状態を維持し続けるために、以下のコマンドをターミナル上で実行します。このコマンドを実行することで、ユーザーのログインとログアウトに対応したコントローラとビューが作成されます。ログインとログアウト機能で、セッションを使用するので、コントローラー名をUserSessionsと設定しています。

$ rails g controller UserSessions new create destroy

2 . 上記で作成したコントローラーにログインとログアウト処理をメソッドとして記載します。

# app/controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
    # ログイン処理
  def create
        # loginメソッドを使って、paramsで取得したemailによるUser検索、パスワードの検証を行い、正常に処理できるとセッションデータにUserレコードのid値を@userに格納する
    @user = login(params[:email], params[:password])

    if @user # @userがnullではない時、ログインできている時
            # ログインが成功すると、ログイン成功のフラッシュメッセージを表示するとともに、ユーザーが要求したページにリダイレクトする。
      redirect_back_or_to(:users, notice: 'Login successful')
    else # @userがnullの時、ログインできていない時
            # ログインに失敗すると、ログイン失敗フラッシュメッセージを表示して、新規登録ページに遷移する。
      flash.now[:alert] = 'Login failed'
      render action: 'new'
    end
  end

    # ログアウト処理
  def destroy
        # logoutメソッドでセッションをリセットする
    logout
    redirect_to(:users, notice: 'Logged out!')
  end
end

loginメソッドで、認証処理が行われる。 @user = login(params[:email], params[:password])でemailによるUser検索、パスワードの検証を行い、正常に処理できるとセッションデータにUserレコードのid値を格納する、という処理が行われている。


logoutメソッドで、セッションをリセットする。


redirect_back_or_toメソッド・・・保存されたURLがある場合そのURLに、ない場合は指定されたURLにリダイレクトします。ユーザーがリクエストしたURLがあるときはそのURLにリクエストを、リクエストURLがない場合は、指定されたURLにリダイレクトします。
例えば、掲示板ページにアクセスしようとしたユーザにログインを要求する場合、require_loginメソッドでユーザをログインページに誘導し、ログインが成功したら、最初に訪れようとしていた掲示板ページにリダイレクトさせるということが可能になる。

3 . ログインフォームを作成する

# app/views/user_sessions/new.html.erb
<h1>Login</h1>

<%= render 'form' %>

<%= link_to 'Back', users_path %>
# app/views/user_sessions/_form.html.erb
<%= form_with url: login_path, method: :post do |f| %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password %>
  </div>
  <div class="actions">
    <%= f.submit "Login" %>
  </div>
<% end %>

4 . ルーティングを定義する

# config/routes.rb
root :to => 'users#index'
resources :users

get 'login' => 'user_sessions#new', :as => :login
post 'login' => "user_sessions#create"
post 'logout' => 'user_sessions#destroy', :as => :logout

5. 現在は、ログアウトしているユーザーでもログインしているユーザーと同じような操作が出来てしまうので、操作の前にログイン認証を行うようにします。ただし、ログアウト時でもできる操作に関しては、Skip処理をすることで、ログイン認証を行わず、操作ができるように設定します。

# app/controllers/application_controller.rb
# 全てのアクション実行前にログイン認証を必須とする。
before_action :require_login 
# app/controllers/users_controller.rb
# index, new, createアクション実行時のみ、ログイン認証処理を必須としない。
skip_before_action :require_login, only: [:index, :new, :create]
# app/controllers/user_sessions_controller.rb
# new, createアクション実行時のみ、ログイン認証を必須としない
skip_before_action :require_login, only: [:new, :create]

6 . ログインしていないユーザーがログインユーザー専用のページにアクセスするのを防ぐため、以下のコードをapplication_controller.rbに記載します。そうすることで、もしログインしていないユーザーがログインユーザー専用のページにアクセスした際、ログインページに遷移します。

# app/controllers/application_controller.rb
private
def not_authenticated
  redirect_to login_path, alert: "Please login first"
end

sorceryを使うと使用できるようになるメソッド

require_login

ログインをしていないユーザーをアクション単位で弾く。アクセスしようとしたURLをセッションに格納し、not_authenticatedを実行するメソッド。以下のようにbefore_actionで指定する。アクションごとに変える場合は、only: :actionを付ける。アクション内の分岐など、もっと細かい単位で弾きたい場合は後述のlogged_in?を使う。

sorceryのモジュール内に定義されているメソッドで、sorcery gemをインストールすることによって、使えるメソッドになる。ApplicationController内に定義することで、全てのアクション実行前に使えるようになる。

before_action :require_login

before_actionの使い方とオプションについて

not_authenticated

先ほどのrequire_login内で、このメソッドも実行される。デフォルトではredirect_to root_path(自動的にルートに飛ばされる)と定義されているが、カスタマイズしたい場合はapplication_controllerで上書きをする。

class ApplicationController < ActionController::Base
  protected

  def not_authenticated
    redirect_to login_url, alert: 'ログインしてください'
  end
end

current_user

現在ログイン中のuserを返す。コントローラ、ビューで使える。

logged_in?

現在ログイン中かどうか、true or falseで返す。コントローラ、ビューで使える。ログインしているかどうかによって場合分けをしたいときに使うことが多い。

今回は、ヘッダーの表示をログインしているか、ログインしていないかで場合分けするために、logged_in?メソッドを使い、実装しました。

<% if logged_in? %>
  <%= link_to 'プロフィール', user_url(current_user) %>
<% else %>
  <%= link_to 'ログイン', login_url %>
<% end %>

sorceryを使わないで、ログインの有無による、表示分けを行う時は、以下を参照

【Rails】ログイン機能を実装する - Qiita

【Sorcery】Sorceryで使えるようになるメソッドとその活用例 - Qiita

第8章 ログイン、ログアウト - Railsチュートリアル

参考

Simple Password Authentication · Sorcery/sorcery Wiki

GitHub - Sorcery/sorcery: Magical Authentication

GitHub - heartcombo/devise: Flexible authentication solution for Rails with Warden.

Rails+Sorceryで認証処理を実装する

【Rails】駆け出しVimmerがSorceryをざっくり説明してみた

第16回 データベースからパスワードが盗まれても問題がなくなる方法

gem sorceryを使ってログイン機能を実装した話 - Qiita

【Rails】ログイン機能を実装 - Qiita

仮想属性を作成する理由が理解できません|teratail

【Sorcery】Sorceryで使えるようになるメソッドとその活用例 - Qiita