管理画面へのログイン機能の実装
実装したいこと
管理者用トップページは前回作成したので、今回は管理画面へのログイン機能を実装していきます。
- 管理者画面へのログインページを作成
- 管理者画面へのログインページを使って一般ユーザーもログインできるが、管理者でない場合は通常トップページへ飛ばされる。
「権限がありません」 - 管理者がログインできたら、管理画面のトップページへリダイレクトされる。
「ログインしました。」 - (管理者or一般ユーザーが) ログインに失敗したら、管理者ログインページへリダイレクトされる。
「ログインに失敗しました。」 - ログアウトしたら、管理画面ログインページへリダイレクトされる。
「ログアウトしました。」
ログイン失敗時はメールアドレスで管理者かどうか判定して、「権限がありません」or「ログインに失敗しました」で判定を分けると勘違いして悩みました。
単純に、管理者ログイン機能は通常のログイン機能を持ち、一般ユーザーも管理者も使用できます。ログイン後に管理者かどうかを判定してフィルタで対応する流れです。
実装の流れ
- コントローラーの設定
- ルーティングの設定
- 管理者ログイン用ビューの作成
コンソールで管理者権限を持つユーザーをつくっておき、実際にログイン機能を試しながら実装していきます。
既に基底クラスのコントローラー等は前回作成しています。
Controller
まず、基底クラスのコントローラーに追記していきます。
他の管理系コントローラーはこのbase_controllerを継承するので、管理系機能全体を通して使いたいメソッドはこちらに記述していきます。
class Admin::BaseController < ApplicationController before_action :check_admin #全体に適用 layout 'admin/layouts/application' private #ログインしてないとき実行される(sorceryのメソッド) def not_authenticated flash[:warning] = 'ログインしてください' redirect_to admin_login_path end #管理者権限がないユーザーを弾く def check_admin redirect_to root_path, warning: '権限がありません' unless current_user.admin? end end
既にSorceryを導入済みです。
application_controllerでbefore_action :require_login
を記述してあるので、ログインしていない場合は自動的にnot_authenticaed
というメソッドを実行します。(Sorceryのヘルパー)
ログインしていないユーザーは管理者用のログインページへリダイレクトさせたいので、not_authenticaed
を設定します。
check_admin
でログイン済のcurrent_user
がadmin
かを判定しています。
before_action :check_adimin
とフィルタを設定し、各アクション前に実行されるようにします。
前回、enum
でroleカラムの整数をadminと定義したので、admin?
が使用できます。
Sorceryメソッドについて参照 https://qiita.com/aiandrox/items/65317517954d8d44d957
SorceryでRailsアプリケーションに認証機能を実装する | Boys Be Engineer 非エンジニアよ、エンジニアになれ
管理者ログイン機能を持つコントローラーを作成していきます。
rails g controller Admin::User_sessions new
class Admin::UserSessionsController < Admin::BaseController #BaseControllerを継承 skip_before_action :require_login, only: %i[new create] skip_before_action :check_admin, only: %i[new create] layout 'admin/layouts/admin_login' #ログインページ用のレイアウトを用意するので宣言 def new; end def create @user = login(params[:email], params[:password]) #Sorceryメソッド。emailとpasswordでログイン認証する。 if @user redirect_to admin_root_path, success: 'ログインしました' else flash.now[:danger] = 'ログインに失敗しました' render :new end end def destroy logout redirect_to admin_login_path, success: 'ログアウトしました' end end
ログインしていない/管理権限がなくても、ログイン認証画面は使用できるようにskip_before_action
でフィルタをスキップさせてあげます。
最初この設定を忘れて、延々とリダイレクトさせてしまいエラーとなりました。
ログインしたら、admin_root_path
へリダイレクトします。そしてadmin/dashboards#index
へ。
もしこのとき管理者でない場合は、先程base_controller
で定義したbefore_action :check_admin
フィルタにより、一般ユーザー用のトップページへリダイレクトされます。
Routing
namespace :admin do root to: 'dashboards#index' get 'login', to: 'user_sessions#new' post 'login', to: 'user_sessions#create' delete 'logout', to: 'user_sessions#destroy' end
View
このようなシンプルなログイン画面を作りたいです。
ログイン画面は、前回インストールしたAdminLTEのテンプレートnode_modules/admin-lte/pages/example/login.html
を参考にします。
SNSログインなど、不要な部分は削除します。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="robots" content="noindex, nofollow"> <title><%= page_title(yield(:title), admin: true) %></title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'admin', media: 'all' %> </head> <body class="hold-transition login-page"> <div> <%= render 'shared/flash_message' %> <%= yield %> </div> </body> </html>
<% content_for(:title, t('.title')) %> #タイトルは「ログイン」 <div class="login-box"> <div class="login-logo"> <h1><%= t('.title') %><h1> </div> <!-- /.login-logo --> <div class="card"> <div class="card-body login-card-body"> <%= form_with url: admin_login_path, local: true do |f| %> <%= f.label :email, User.human_attribute_name(:email) %> <div class="input-group mb-3"> <%= f.email_field :email, class: 'form-control', placeholder: 'Email' %> <div class="input-group-append"> <div class="input-group-text"> <span class="fas fa-envelope"></span> </div> </div> </div> <%= f.label :password, User.human_attribute_name(:password) %> <div class="input-group mb-3"> <%= f.password_field :password, class: 'form-control', placeholder: 'Password' %> <div class="input-group-append"> <div class="input-group-text"> <span class="fas fa-lock"></span> </div> </div> </div> <div class='row'> <div class='col-12'> <%= f.submit t('defaults.login'), class: 'btn btn-block btn-primary' %> </div> </div> <% end %> </div> </div> </div>
送信先は、url: admin_login_path
です。ログイン機能なのでモデルは持ちません。
<%= form_with url: admin_login_path, local: true do |f| %>
ログインボタンを幅広にしたい…
btn btn-block btn-primary
で解決ラベルと入力フォームを縦に並べたかったのに、一行になってしまっていた。
<div class="input-group mb-3">
で全体を囲っていたのを、ラベル部分を外して囲ったら解決した。(でもよく分かっていない)
エラーの記録
反省用に記録していきます。
ログイン後にdashboards/indexに自動遷移しない。URLを直接打ち込んだら飛ぶからログインはできている。なぜ??
<%= form_with url: admin_login_path, lacal: true do |f| %>
lacalになってた…。これ以外にも、local: trueを忘れてログインできない失敗をよくするので注意。
レイアウト名について。ログイン画面をnew.html.erbで作らず、admin_login.html.erbにしてたけど、newのがアクション名と一致するしわかりやすい◎
app/asssets/layout
の中にadmin用layoutを作っていたけど、adminディレクトリ内にadimin/layout/application.html.erb
を作るほうが管理しやすい◎
sessions_controller.rb
で最初に私が誤っていた実装
そもそも、管理者しか使えないログイン認証画面だと思いこんでおり、前提を誤っていた。
管理者のメールだけど、パスワードを間違えた場合→ログインできません
そもそもメールも違う場合→権限がありません
先にbefore_actionのフィルターで管理者チェックをする?
などど混乱してしまいました。
def create @user = login(params[:email], params[:password]) if @user&.admin? #謎のコード redirect_to admin_path, success: 'ログインしました' else redirect_to root_path, danger: 'ログインに失敗しました' end end
ログイン機能自体を忘れているのでまたチュートリアルでも復習したほうがいいかも。