Programming Journal

学習したことの整理用です。

管理画面へのログイン機能の実装

実装したいこと 

管理者用トップページは前回作成したので、今回は管理画面へのログイン機能を実装していきます。

  • 管理者画面へのログインページを作成
  • 管理者画面へのログインページを使って一般ユーザーもログインできるが、管理者でない場合は通常トップページへ飛ばされる。
    「権限がありません」
  • 管理者がログインできたら、管理画面のトップページへリダイレクトされる。
    「ログインしました。」
  • (管理者or一般ユーザーが) ログインに失敗したら、管理者ログインページへリダイレクトされる。
    「ログインに失敗しました。」
  • ログアウトしたら、管理画面ログインページへリダイレクトされる。
    「ログアウトしました。」

ログイン失敗時はメールアドレスで管理者かどうか判定して、「権限がありません」or「ログインに失敗しました」で判定を分けると勘違いして悩みました。
単純に、管理者ログイン機能は通常のログイン機能を持ち、一般ユーザーも管理者も使用できます。ログイン後に管理者かどうかを判定してフィルタで対応する流れです。

実装の流れ

  • コントローラーの設定
  • ルーティングの設定
  • 管理者ログイン用ビューの作成

コンソールで管理者権限を持つユーザーをつくっておき、実際にログイン機能を試しながら実装していきます。

既に基底クラスのコントローラー等は前回作成しています。

study-diary.hatenadiary.jp

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_useradminかを判定しています。
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

f:id:Study-Diary:20200903095146p:plain
routes.rb

View

このようなシンプルなログイン画面を作りたいです。

f:id:Study-Diary:20200903083243p:plain
ログイン画面

ログイン画面は、前回インストールした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

ログイン機能自体を忘れているのでまたチュートリアルでも復習したほうがいいかも。