sorceryのインストール
Gemfile
gem 'sorcery'
Userモデルを作成する
-
bundle exec rails g sorcery:install
を実行すると、以下のファイルが作成される。- migrationファイル(db/migrate/〇〇〇〇_sorcery_core.rb)
- initializerファイル(config/initializers/sorcery.rb)
- Userモデル
-
暗号化されたパスワード(
crypted_password
)やソルト(salt
)というカラムをユーザーに編集/閲覧させたくない。つまりビューで使用したくない。- 以下のように、「仮想的な」passwordフィールドを使用し、データベースに暗号化される前のパスワードをビューで扱う。
→このpassword属性はカラムに対応していないため、平文のパスワード情報がDBに保存されることは無い。
→パスワードは暗号化され、DBに保存される。
- 以下のように、「仮想的な」passwordフィールドを使用し、データベースに暗号化される前のパスワードをビューで扱う。
app/views/user_sessions/new.html.erb
<%= f.label :password %>
<%= f.password_field :password%>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation%>
- Userモデルに以下のように記述することで、Userクラスがsorceryを使えるようになる。
-
validates :password, confirmation: true
で、passwordというDBに存在しない仮想的な属性(virtual attributes)が追加される。 -
if: -> { new_record? || changes[:crypted_password] }
で、ユーザーがパスワード以外のプロフィール項目を更新したい場合に、パスワードの入力を省略できるようになる。
-
app/models/user.rb
class User < ActiveRecord::Base
authenticates_with_sorcery!
validates :password, length: { minimum: 8 }, 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
認証機能
- ルーティング
config/routes.rb
get '/login', to: 'user_sessions#new', as: :login
post '/login', to: 'user_sessions#create'
delete 'logout', to: 'user_sessions#destroy', as: :logout
- require_loginメソッド
- application_controller.rbに記述し共通化。
- 各コントローラで
only
を使用し、アクションごとにログインを要求する。
- not_authenticatedメソッド
- ログインできなかった(認証されなかった)場合に、このメソッドに記述した処理が行われる。
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :require_login
private
def not_authenticated
redirect_to login_path, danger: "ログインしてください"
end
end
-
login
メソッドで、認証処理が行われる。-
@user = login(params[:email], params[:password])
でemailによるUser検索、パスワードの検証を行い、正常に処理できるとセッションデータにUserレコードのid値を格納する、という処理が行われている。
-
-
logout
メソッドで、セッションをリセットする。 -
redirect_back_or_to
メソッド・・・例えば、掲示板ページにアクセスしようとしたユーザにログインを要求する場合、require_login
メソッドでユーザをログインページに誘導し、ログインが成功したら、最初に訪れようとしていた掲示板ページにリダイレクトさせるということが可能になる。
app/controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
skip_before_action :require_login, only: %i[create new]
def new; end
def create
@user = login(params[:mail], params[:password])
if @user
redirect_back_or_to root_path, success: "ログインに成功しました"
else
flash.now[:danger] = "ログインに失敗しました"
render :new
end
end
def destroy
logout
redirect_back_or_to root_path, success: "ログアウトしました"
end
end
app/views/user_sessions/new.html.erb
<%= form_with url: login_path, local:true do |f| %>
<%= f.label :email, "メールアドレス" %>
<%= f.text_field :email%>
<%= f.label :password, "パスワード" %>
<%= f.password_field :password%>
<%= f.submit "ログイン" %>
<% end %>
ビューでログイン判定を使用し、表示するテンプレートを変える。
- sorceryの
logged_in?
メソッドを使用。
app/views/layouts/application.html.erb
<% if logged_in? %>
// ログイン後のテンプレートを表示
<% else %>
// ログイン前のテンプレートを表示
<% end %>
[参考]上記で使用したsorceryが用意してくれているメソッドの一例
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
おわり
以上です!
READMEを読んでおけば、なんとなくでsorceryのログイン機能を使えちゃったりもしますが、
- ステートレスなHTTP通信上における一連のリクエスト/レスポンスの中で、どのようにユーザーの状態を維持させているのか?
- なぜpasswordという仮想的な属性をフォームで使用するのか?
などログイン機能の仕組みを理解した上で実装することが重要だと思います!