【Rails】Sorceryでログイン機能を実装する

Sourceryでログイン機能を実装してみました。
`Devise`よりカスタマイズしやすそうで、自分で実装するより手軽なので結構好印象です。
フレンドリーフォワーディングが簡単に実装できるのが良いですね。

(3月12日追記）4年以上更新されていないので、system specが使えません。

##セットアップ
```rb:Gemfile
gem 'sorcery'
```
```zsh:terminal
$ bundle install
$ rails g sorcery:install
```
Userモデルとマイグレーションファイル、設定ファイルなどもろもろ生成されます。

```zsh:terminal
$ rails db:migrate
```

モデルにバリデーションを設定

```model/user.rb
class User < ApplicationRecord
  authenticates_with_sorcery!
  validates :email, uniqueness: true, presence: true
  validates :password, presence: true
end
```

`application_controller.rb`にbefore_actionとメソッドを定義

```application_controller.rb
class ApplicationController < ActionController::Base
  before_action :require_login #sorceryが作成するメソッド。ログインしてない時not_authenticatedメソッドを発火する

  protected

  def not_authenticated
    redirect_to login_url
  end
end
```

##新規ユーザー登録
```users_controller.rb
class UsersController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to root_url
    else
      render :new
    end
  end

  private

  def user_params
    params.require(:user).permit(:email, :password)
  end
end
```
```erb:new.html.erb
<%= form_with model: @user, local: true do |f| %>
  <%= f.label :email, 'メールアドレス' %>
  <%= f.text_field :email  %>

  <%= f.label :password, 'パスワード' %>
  <%= f.password_field :password  %>  

  <%= f.label :password_confirmation, 'パスワード確認' %>
  <%= f.password_field :password_confirmation  %>     
  
  <%= f.submit '登録' %>  
<% end %>
```
```route.rb
resources :users, only: %i[index create]
```

##ログイン
`login`：見つかったユーザーでログインする
`logout`：ログアウトする
`redirect_back_or_to`：保存されたURLがある場合そのURLに、ない場合は指定されたURLにリダイレクトする。（フレンドリーフォワーディング）

```sessions_controller.rb
class UsersController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]

  def index
    @user = User.new
  end

  def create
    if @user = login(params[:email], params[:password])
      redirect_back_or_to(hoge_url, notice: 'ログインしました')
    else
      flash[:alert] = 'ログイン失敗'
      render :new
    end
  end

  def destory
    logout
    redirect_to(root_url, notice: 'ログアウトしました')
  end
end
```
```erb:new.html.erb
<%= form_with url: sessions_path, local: true do |f| %>
  <%= f.label :email, 'メールアドレス' %>
  <%= f.text_field :email  %>

  <%= f.label :password, 'パスワード' %>
  <%= f.password_field :password  %>  
    
  <%= f.submit "ログイン" %>        
<% end %>
```
```routes.rb
get '/login' => 'sessions#new'
post '/login' => 'sessions#create'
post '/logout' => 'sessions#destroy'
```

##公式よりメソッドを抜粋
```rb
require_login # This is a before action
login(email, password, remember_me = false)
auto_login(user) # Login without credentials
logout
logged_in? # Available in views
current_user # Available in views
redirect_back_or_to # Use when a user tries to access a page while logged out, is asked to login, and we want to return him back to the page he originally wanted
@user.external? # Users who signed up using Facebook, Twitter, etc.
@user.active_for_authentication? # Add this method to define behaviour that will prevent selected users from signing in
@user.valid_password?('secret') # Compares 'secret' with the actual user's password, returns true if they match
User.authenticates_with_sorcery!
```

[Sorcery/sorcery](https://github.com/Sorcery/sorcery)

##テスト
4年以上更新されていないgemなので、system specには対応していないようです。（3月12日時点）
###フィーチャースペック
```spec/support/authentication.rb
#sessions_urlの部分をログイン画面のURLに合わせる
#password = 'login'の部分は適当な文字列に変えてもなぜか動く
module AuthenticationForFeatureRequest
  def login user, password = 'login'
    user.update_attributes password: password
    page.driver.post sessions_url, {email: user.email, password: password}
    visit root_url
  end
end
```
```spec/support/authentication.rb
RSpec.configure do |config|
  # ...
  config.include AuthenticationForFeatureRequest, type: :feature
  # ...
end
```
```spec/factories/users.rb
FactoryBot.define do
  factory :user do
    email { 'test1@test.com' }
    password { 'hogehoge' }
    salt { 'salt' }
    crypted_password { Sorcery::CryptoProviders::BCrypt.encrypt('hogehoge', 'salt') }
  end
end
```
```spec/features/hoge_spec.rb
RSpec.feature "hoge", type: :feature do
  let(:user) { create(:user) }
  it 'ユーザー情報編集' do
    login(user)
    click_link 'マイページ'
    #...
  end
end
```
##参考
- [シンプル認証gem sorceryを完全入門するで！！](https://qiita.com/babashunsu/items/9937b0a2e08d318edece)
- [SorceryでRailsアプリケーションに認証機能を実装する](https://kurose.me/sorcery-rails/)
- [NoamB/sorcery](https://github.com/NoamB/sorcery/wiki/Testing-Rails)