完成した統一認証システム

プロ太先生、Quick Mendanの認証システムが完成しました!講師も生徒も同じパターンで実装されていて、すごくスッキリしています!

素晴らしいね!どんな設計になっているのか、詳しく見てみようか。

はい!まず全体像から説明します。

統一された認証フロー

# 講師認証(AuthController)
GET  /staff/login     → staff_login(ログイン画面表示)
POST /staff/login     → staff_authenticate(認証処理)
DELETE /staff/logout  → staff_logout(ログアウト処理)

# 生徒認証(StudentsController)
GET  /student/login   → login(ログイン画面表示)
POST /student/login   → authenticate(認証処理)
DELETE /student/logout → logout(ログアウト処理)

おお、完全に同じパターンだね!URLの構造も統一されている。

セッションベース認証の実装

講師認証の実装

# app/controllers/auth_controller.rb
class AuthController < ApplicationController
  def staff_authenticate
    username = params[:username]
    password = params[:password]
    
    # カスタム認証メソッドで検索
    teacher = Teacher.find_for_database_authentication(email: username)
    
    if teacher && teacher.valid_password?(password)
      # セッションに講師IDを保存
      session[:teacher_id] = teacher.id
      redirect_to staff_dashboard_path, notice: 'ログインしました'
    else
      flash.now[:alert] = 'ユーザー名またはパスワードが正しくありません'
      render :staff_login
    end
  end

  def staff_logout
    # セッションをクリア
    session[:teacher_id] = nil
    redirect_to root_path, notice: 'ログアウトしました'
  end

  private

  def current_staff_user
    @current_staff_user ||= session[:teacher_id] && Teacher.find_by(id: session[:teacher_id])
  end
end

生徒認証の実装

# app/controllers/students_controller.rb
class StudentsController < ApplicationController
  def authenticate
    student_number = params[:student_number]
    password = params[:password]
    
    # カスタム認証メソッドで検索
    student = Student.find_for_database_authentication(email: student_number)
    
    if student && student.valid_password?(password)
      # セッションに生徒IDを保存
      session[:student_id] = student.id
      redirect_to student_dashboard_path, notice: 'ログインしました'
    else
      flash.now[:alert] = '生徒番号またはパスワードが正しくありません'
      render :login
    end
  end

  def logout
    # セッションをクリア
    session[:student_id] = nil
    redirect_to root_path, notice: 'ログアウトしました'
  end

  private

  def current_student
    @current_student ||= session[:student_id] && Student.find_by(id: session[:student_id])
  end
end

講師も生徒も全く同じパターンですね!セッションに保存して、ログアウト時にクリアする。シンプルです!

モデルの統一されたカスタム認証

Teacher モデル

# app/models/teacher.rb
class Teacher < ApplicationRecord
  # ユーザー名でログインするための設定
  def self.find_for_database_authentication(warden_conditions)
    # warden_conditions = {email: "shibaguti"}
    conditions = warden_conditions.dup
    if (email = conditions.delete(:email))
      # emailキーで渡された値をuser_login_nameで検索
      where(user_login_name: email).first
    else
      # emailキー以外の場合はnilを返す
      nil
    end
  end
end

Student モデル

# app/models/student.rb
class Student < ApplicationRecord
  # 生徒番号でログインするための設定
  def self.find_for_database_authentication(login_conditions)
    # login_conditions = {email: "2024001"}
    search_conditions = login_conditions.dup
    if (student_number_input = search_conditions.delete(:email))
      # emailキーで渡された値をstudent_numberで検索
      where(["student_number = :student_number", { student_number: student_number_input }]).first
    else
      # emailキー以外の場合はnilを返す
      nil
    end
  end
end

なるほど!両方とも :email キーを受け取って、それぞれの認証フィールドで検索している。Deviseとの互換性を保ちながら、カスタム認証を実現している設計だね。

ApplicationHelper の統一設計

# app/helpers/application_helper.rb
module ApplicationHelper
  # 現在ログインしている講師を取得
  def current_teacher
    @current_teacher ||= session[:teacher_id] && Teacher.find_by(id: session[:teacher_id])
  end

  # 現在ログインしている生徒を取得(講師方式と統一)
  def current_student
    @current_student ||= session[:student_id] && Student.find_by(id: session[:student_id])
  end

  # 講師がログインしているかチェック
  def teacher_signed_in?
    current_teacher.present?
  end

  # 生徒がログインしているかチェック(講師方式と統一)
  def student_signed_in?
    current_student.present?
  end

  # 管理者がログインしているかチェック
  def admin_signed_in?
    current_teacher&.admin?
  end

  # 現在のユーザーが管理者かどうか
  def current_user_is_admin?
    current_teacher&.admin?
  end
end

ヘルパーメソッドも完全に対応しています。講師用も生徒用も同じパターンで実装されています!

認証チェックの統一

講師用認証チェック

# AuthController
before_action :require_staff, only: [:staff_dashboard]

private

def require_staff
  unless current_staff_user
    redirect_to staff_login_path, alert: 'ログインが必要です'
  end
end

生徒用認証チェック

# StudentsController
before_action :require_student_login, only: [:dashboard]

private

def require_student_login
  unless current_student
    redirect_to student_login_path, alert: 'ログインが必要です'
  end
end

認証チェックも同じパターンだね。必要なアクションにのみ before_action を設定して、ログインが必要な場合は適切なログインページにリダイレクトする。

フォームの統一設計

講師ログインフォーム

<!-- app/views/auth/staff_login.html.erb -->
<%= form_with url: staff_login_path, method: :post, local: true do |f| %>
  <%= f.text_field :username, placeholder: "ユーザー名" %>
  <%= f.password_field :password, placeholder: "パスワード" %>
  <%= f.submit "ログイン" %>
<% end %>

生徒ログインフォーム

<!-- app/views/students/login.html.erb -->
<%= form_with url: student_login_path, method: :post, local: true do |f| %>
  <%= f.text_field :student_number, placeholder: "生徒番号" %>
  <%= f.password_field :password, placeholder: "パスワード(9999)" %>
  <%= f.submit "ログイン" %>
<% end %>

フォームの構造も同じです!フィールド名だけが違って、送信先のURLも統一されたパターンです!

この設計のメリット

保守性の向上

# 同じパターンなので、理解しやすく修正しやすい
# 講師用
session[:teacher_id] = teacher.id

# 生徒用  
session[:student_id] = student.id

拡張性の高さ

# 将来、管理者認証を追加する場合も同じパターン
session[:admin_id] = admin.id

def current_admin
  @current_admin ||= session[:admin_id] && Admin.find_by(id: session[:admin_id])
end

Deviseへの依存排除

複雑なDevise設定に依存せず、シンプルなRailsの基本機能だけで実装している。

パフォーマンスの向上

# シンプルなセッション管理 = 高速
@current_student ||= session[:student_id] && Student.find_by(id: session[:student_id])

実際の動作フロー

実際にユーザーがログインする時の流れを確認してみましょう!

生徒ログインの場合

  1. ログインページアクセス: GET /student/login
  2. フォーム送信: POST /student/login
  3. StudentsController#authenticate 実行
   student_number = params[:student_number]  # "2024001"
   student = Student.find_for_database_authentication(email: student_number)
   if student && student.valid_password?(password)
     session[:student_id] = student.id  # セッションに保存

4. ダッシュボードにリダイレクト: GET /student/dashboard
5. 認証チェック

       def current_student
         @current_student ||= session[:student_id] && Student.find_by(id: session[:student_id])
       end

    講師ログインも全く同じ流れで、フィールド名とセッションキーが違うだけです!!

    まとめ

    この統一認証システムで学んだことをまとめてみます!

    設計の原則

    1. 統一性: 同じパターンを使い回す
    2. シンプルさ: 複雑な機能に依存しない
    3. 可読性: コードが理解しやすい
    4. 拡張性: 新しい認証タイプを追加しやすい
    5. 保守性: メンテナンスが容易

    技術のポイント

    # 1. セッションベース認証
    session[:user_id] = user.id
    
    # 2. カスタム認証メソッド
    Model.find_for_database_authentication(email: identifier)
    
    # 3. 統一されたヘルパーメソッド
    def current_user
      @current_user ||= session[:user_id] && User.find_by(id: session[:user_id])
    end
    
    # 4. 一貫した認証チェック
    before_action :require_authentication, only: [:protected_action]

    素晴らしいまとめだね!この認証システムは、シンプルで美しく、実用的だ。Railsの基本を深く理解できる、とても良い学習材料になったね。

    はい!Deviseの便利さも分かりましたが、基本から作ることでRailsの認証の仕組みがよく理解できました。ありがとうございました!