見出し画像

sorceryによるログイン機能

gemを用いて簡単にログイン機能が実装できるので、まとめてみた。

・gemに記述(bundle install)

gem 'sorcery'

・ターミナルでsoceryをインストール

rails generate sorcery:install

このコマンドによって以下のファイルが生成される。
app/models/user.rb
db/migrate/XXXXXXXXX_sorcery_core.rb

・db/migrate/XXXXXXXXX_sorcery_core.rbの中身を見てみる。
rails db:migrateの実行 (今回はfist_name, last_nameカラムを追加)

class SorceryCore < ActiveRecord::Migration[5.2]
 def change
   create_table :users do |t|
     t.string :email,            null: false
     t.string :crypted_password
     t.string :salt
     t.string :first_name,       null: false #追加
     t.string :last_name,        null: false #追加
     t.timestamps                null: false 
   end
   add_index :users, :email, unique: true
 end
end

 上記のcrypted_passwordは、パスワードを暗号化することによってセキュリティを強化する(passwordとpassword_confirmationが使える)。これによって、DBに保存されるのは、実際にユーザーが登録したパスワードがそのまま保存されるのではなく、ハッシュ化された(暗号化された)ものがDBに保存される。

→ model にhas_secure_passwordを記述するのと同じ働き

 次にsaltカラムは、標準の暗号化では、「ソルト」を使用してパスワードハッシュの安全性を高めている。 Sorceryでは、パスワードの末尾にランダムな文字列を結合し、その文字列をソルトフィールドに記憶することでこれを行う働きを持っている。

・モデルにvalidationを追記する

class User < ApplicationRecord 
 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
 validates :email, presence: true
 validates :first_name, presence: true, length: { maximum: 255 }
 validates :last_name, presence: true, length: { maximum: 255 }
end

 上記の authenticates_with_sorcery! は、Userモデルにsorceryによる認証機能を持たせている。

 また、if: -> { new_record? || changes[:crypted_password] }で、new_record?の部分については、インスタンスが新規に作成されるものかどうかを判定するActive Recordのメソッドの一つであり、オブジェクトがすでにDBにあるかを確認できる。
 if: -> については、条件付きバリデーションのオプションに当たるもので、特定の条件でバリデーションを行なうべきである場合に使う。
(Railsガイド参照)

 つまり、if: -> { new_record? || changes[:crypted_password] }で、ユーザーがパスワード以外のプロフィール項目を(メールアドレスや名前)を更新したい場合に、パスワードの入力を省略できるようになる。

・コントローラーを編集

(app/controllers/user_sessions_controller.rb)

class UserSessionsController < ApplicationController
 def new
 end
 def create
   @user = login(params[:email], params[:password])
   if @user
     redirect_back_or_to root_path, success: t('.success')
   else
     flash.now[:danger] = t('.fail')
     render :new
   end
 end
 def destroy
   logout
   redirect_to root_path, success: t('.success')
 end
end

 createアクション内にloginメソッド、destroyアクション内にlogoutメソッドが使われているが、これがsoceryが持っているメソッド。(参考)

 loginメソッドは、emailによるUser検索、パスワードの検証を行い、正常に処理できるとセッションデータにUserレコードのid値を格納する、という処理が行われている。logoutメソッドは、セッションをリセットしている。

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

・ルーティングを編集

Rails.application.routes.draw do
 root 'static_pages#top'
 
 get 'login', to: 'user_sessions#new' #追加
 post 'login', to: 'user_sessions#create' #追加
 delete 'logout', to: 'user_sessions#destroy' #追加

 resources :users, only: %i[new create]
end

・viewを編集

(app/views/layouts/application.html.erb)

<!DOCTYPE html><html> <head>
 <title>Rails_App</title> 
   <%= csrf_meta_tags %>   
   <%= csp_meta_tag %>
   <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
   <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
 </head>
 <body>
   <% if logged_in? %> #ログインしているかを判断(soceryメソッド)
     <%= render 'shared/header'%> #ログインした時のテンプレート
   <% else %>
     <%= render 'shared/before_login_header' %> #ログインしてない時のテンプレート
   <% end %>
   <%= render 'shared/flash_message' %>
   <%= yield %>
   <%= render 'shared/footer' %>
 </body>
</html>​

・参考

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

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

公式リファレンス

・soceryが持っているメソッド

module InstanceMethods # To be used as before_action. # Will trigger auto-login attempts via the call to logged_in? # If all attempts to auto-login fail, the failure callback will be called. def require_login   unless logged_in?     session[:return_to_url] = request.url if Config.save_return_to_url && request.get? && !request.xhr?     send(Config.not_authenticated_action)   end end
 # Takes credentials and returns a user on successful authentication.
 # Runs hooks after login or failed login.
 def login(*credentials)
   @current_user = nil
   user_class.authenticate(*credentials) do |user, failure_reason|
     if failure_reason
       after_failed_login!(credentials)
       yield(user, failure_reason) if block_given?
       return
     end
     old_session = session.dup.to_hash
     reset_sorcery_session
     old_session.each_pair do |k, v|
       session[k.to_sym] = v
     end
     form_authenticity_token
     auto_login(user)
     after_login!(user, credentials)
     block_given? ? yield(current_user, nil) : current_user
   end
 end
def logout
 if logged_in?
   user = current_user
   before_logout!
   @current_user = nil
   reset_sorcery_session
   after_logout!(user)
 end
end
def logged_in?
 !!current_user
end
def current_user
 unless defined?(@current_user)
   @current_user = login_from_session || login_from_other_sources || nil
 end
 @current_user
end
# used 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.
def redirect_back_or_to(url, flash_hash = {})
 redirect_to(session[:return_to_url] || url, flash: flash_hash)
 session[:return_to_url] = nil
end
# The default action for denying non-authenticated users.
# You can override this method in your controllers,
# or provide a different method in the configuration.
def not_authenticated
 redirect_to root_path
end
# login a user instance
#
# @param [<User-Model>] user the user instance.
# @return - do not depend on the return value.
def auto_login(user, _should_remember = false)
 session[:user_id] = user.id.to_s
 @current_user = user
end

この記事が気に入ったらサポートをしてみませんか?