sorceryを導入しログイン機能を実装する
gem sorceryを用いることでログイン機能に関する便利なメソッドが利用出来るようになります。 ログイン, ログアウト機能を実装し使用できるメソッドを確認していきます。
- 新規Railsアプリを作成しsorceryを導入する
- Userモデルのバリデーション
- UsersController作成
- ログイン機能を作成
- ログアウト機能を作成
- ログイン状態によりアクセス制御をする
- 使用出来るメソッド
新規Railsアプリを作成しsorceryを導入する
まずはRailsアプリを新規作成します。
rails new sorcery_app
sorceryを導入するためにGemfileに以下を追記
gem 'sorcery'
bundle installします。
bundle install --path vendor/bundle
以下のコマンドを入力しsorceryの必要なファイルを作成します。
rails g sorcery:install
マイグレーションファイルが作成されました。
# db/migrate/***_sorcery_core.rb class SorceryCore < ActiveRecord::Migration[6.1] def change create_table :users do |t| t.string :email, null: false t.string :crypted_password t.string :salt t.timestamps null: false end add_index :users, :email, unique: true end end
マイグレーションしてUserモデルを作成します。
rails db:migrate
Userモデルのバリデーション
Userモデルのファイルが先程の「rails g sorcery:install」コマンドを入力した際に作成されています。 公式wikiを参考にバリデーションを追記します。
# app/models/user.rb 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 end
UsersController作成
scaffoldを使ってCRUD機能を備えたコントローラを作成します。
rails g scaffold_controller user email:string crypted_password:string salt:string create app/controllers/users_controller.rb invoke erb create app/views/users create app/views/users/index.html.erb create app/views/users/edit.html.erb create app/views/users/show.html.erb create app/views/users/new.html.erb create app/views/users/_form.html.erb invoke resource_route route resources :users invoke test_unit create test/controllers/users_controller_test.rb create test/system/users_test.rb invoke helper create app/helpers/users_helper.rb invoke test_unit invoke jbuilder create app/views/users/index.json.jbuilder create app/views/users/show.json.jbuilder create app/views/users/_user.json.jbuilder
railsサーバを立ち上げて試しに「localhost:3000/users」にアクセスするとusers#indexアクションが実行されていることが確認出来ます。
「localhost:3000/users/new」にアクセスすると以下のフォームが表示されます。
「crypted_password」カラムには素のパスワードは入っておらず暗号化された文字列が入るようになっています。 sorceryでは「password」「password_confirmation」という仮想カラムが用意されておりフォームもこの2つを入力出来るように変更する必要があります。
# 以下暗号化されているかの確認 rails c > user = User.new > user.email = 'hoge@foo.bar' > user.password = 'foobar' > user.password_confirmation = 'foobar' > user.save > user.crypted_password => "$2a$10$pEn3qXUSh.cQ/UfdbalRReH2cpVHZS4KJAiqePTSiYLcAqXe8ltf6"
フォームを変更します。
# app/views/users/_form.html.erb <%= form_with(model: user) do |form| %> <!-- 中略 --> <div class="field"> <%= form.label :email %> <%= form.text_field :email %> </div> <div class="field"> <%= form.label :password %> <%= form.password_field :password %> </div> <div class="field"> <%= form.label :password_confirmation %> <%= form.password_field :password_confirmation %> </div> <div class="actions"> <%= form.submit %> </div> <% end %>
UsersControllerのuser_paramsメソッドを変更します。
# app/controllers/users_controller.rb class UsersController < ApplicationController #... private # ... def user_params params.require(:user).permit(:email, :password, :password_confirmation, :salt) end end
再度「localhost:3000/users/new」にアクセスし動作確認をしてみます。
Userモデルの作成が出来ました。
ログイン機能を作成
ログイン・ログアウト機能を作成していきます。 以下のコマンドを入力してUserSessionsControllerを作成します。
rails g controller UserSessions new create destroy create app/controllers/user_sessions_controller.rb route get 'user_sessions/new' get 'user_sessions/create' get 'user_sessions/destroy' invoke erb create app/views/user_sessions create app/views/user_sessions/new.html.erb create app/views/user_sessions/create.html.erb create app/views/user_sessions/destroy.html.erb invoke test_unit create test/controllers/user_sessions_controller_test.rb invoke helper create app/helpers/user_sessions_helper.rb invoke test_unit invoke assets invoke scss create app/assets/stylesheets/user_sessions.scss
ログインフォームを作成します。
# 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 %> <%= f.text_field :email %> </div> <div class="field"> <%= f.label :password %> <%= f.password_field :password %> </div> <div class="actions"> <%= f.submit "Login" %> </div> <% end %>
ルーティングを定義します。
# config/routes.rb Rails.application.routes.draw do get 'login', to: 'user_sessions#new' post 'login', to: 'user_sessions#create' delete 'logout', to: 'user_sessions#destroy' resources :users end
UserSessionsControllerのcreateアクションを作っていきます。
# app/controllers/user_sessions_controller.rb class UserSessionsController < ApplicationController def new; end def create # sorceryのloginメソッドを用いてuserを取得 @user = login(params[:email], params[:password]) if @user redirect_back_or_to users_path, notice: 'Login successful' else flash.now[:alert] = 'Login failed' render :new end end def destroy end end
「localhost:3000/login」にアクセスし動作確認を行います。
無事ログイン処理が作成出来ました!
現状だとログイン出来ているか分かりづらいのでヘッダーを追加してみます。 current_userメソッドは現在ログインしているユーザを取得出来ます。 logged_in?メソッドは現在ログインしているか真偽状態を確認出来ます。
# app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <!-- 中略 --> </head> <body> <nav> <% if logged_in? %> 現在のユーザ: <%= current_user.email %> <%= link_to 'ログアウト', logout_path %> <% else %> <%= link_to 'ログイン', login_path %> <% end %> </nav> <%= yield %> </body> </html>
こんな感じになりました。
ログアウト機能を作成
UserSessionsControllerのdestroyアクションを作っていきます。 logoutは現在ログインしているセッションを削除する(= ログイン状態では無くす)メソッドになります。
# app/controllers/user_sessions_controller.rb class UserSessionsController < ApplicationController #... def destroy logout redirect_to users_path, notice: 'Logged out!' end end
ログアウトのリンクをmethod: :deleteと定義しDELETEリクエストを送るよう変更する。
# app/views/layouts/application.html.erb <!DOCTYPE html> <html> <head> <!-- 中略 --> </head> <body> <nav> <% if logged_in? %> 現在のユーザ: <%= current_user.email %> <%= link_to 'ログアウト', logout_path, method: :delete %> <% else %> <%= link_to 'ログイン', login_path %> <% end %> </nav> <%= yield %> </body> </html>
ではログアウトリンクをクリックしてみます。 無事ログアウト処理が完了しました。
ログイン状態によりアクセス制御をする
例えば今回の例ではUsersControllerのedit, destroyアクションはログイン状態じゃないと実行出来ないようにしたいとする。
まず、ApplicationControllerにログイン状態じゃなかった時のリダイレクト先を定義する。 メソッド名はsorceryで指定のものがあるため以下と同じように記載すること。
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base private def not_authenticated redirect_to login_path, alert: "Please login first" end end
次にアクセス制御したいアクションに対しrequire_loginメソッドをbefore_actionで実行するよう定義する。
# app/controllers/users_controller.rb class UsersController < ApplicationController before_action :require_login, only: %i[edit destroy] #... end
以上でログインしていない状態で指定のアクションを実行するとリダイレクトするようになりました。
使用出来るメソッド
sorceryのAPI情報は公式ドキュメントに記載されています。
# README.md API Summary Coreより引用 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!