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>
・参考
・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
この記事が気に入ったらサポートをしてみませんか?