Deviseがやっていること ‐ Rails認証gem解説 - nyg1971/business_api GitHub Wiki

Deviseがやっていること - Rails認証gem解説

📋 概要

Deviseは Rails の認証システムを簡単に構築できる定番gemです。今回のJWT認証システムでも基盤部分として活用しています。

🔐 Deviseの主要機能

1. パスワード暗号化・検証

# 1. パスワード暗号化・検証
user.valid_password?('password123')  # ← bcryptでハッシュ化して比較

# 2. ユーザー認証のヘルパーメソッド
user.authenticate('password123')

# 内部では bcrypt を使用してパスワードをハッシュ化
# 平文パスワードは保存せず、ハッシュ値のみDB保存

2. バリデーション自動追加

# Deviseが自動で追加するバリデーション
validates :email, presence: true, uniqueness: true
validates :password, length: { minimum: 6 }

# メール形式のバリデーション
validates :email, format: { with: /\A[^@\s]+@[^@\s]+\z/ }

# パスワード確認(password_confirmation)のバリデーション
validates :password_confirmation, presence: true, if: :password_required?

3. データベースカラム自動追加

# Deviseが自動生成するマイグレーション
class DeviseCreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable(パスワードリセット機能)
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable(ログイン記憶機能)
      t.datetime :remember_created_at

      ## Trackable(ログイン追跡機能)
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ## Confirmable(メール確認機能)
      t.string   :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
      t.string   :unconfirmed_email

      ## Lockable(アカウントロック機能)
      t.integer  :failed_attempts, default: 0, null: false
      t.string   :unlock_token
      t.datetime :locked_at

      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    add_index :users, :confirmation_token,   unique: true
    add_index :users, :unlock_token,         unique: true
  end
end

4. ルーティング自動生成(今回は使用していない)

# config/routes.rb で devise_for を使用した場合
devise_for :users

# 以下のルートが自動生成される
#                   Prefix Verb   URI Pattern                    Controller#Action
# new_user_session GET    /users/sign_in                 devise/sessions#new
# user_session     POST   /users/sign_in                 devise/sessions#create
# destroy_user_session DELETE /users/sign_out           devise/sessions#destroy
# new_user_registration GET    /users/sign_up            devise/registrations#new
# user_registration POST   /users                        devise/registrations#create
# edit_user_registration GET    /users/edit              devise/registrations#edit
# user_registration PATCH  /users                        devise/registrations#update
# new_user_password GET    /users/password/new           devise/passwords#new
# edit_user_password GET    /users/password/edit         devise/passwords#edit
# user_password    PATCH  /users/password               devise/passwords#update
# POST   /users/password               devise/passwords#create

🛠️ 今回のプロジェクトでのDeviseの使用方法

User モデルでの設定

# app/models/user.rb
class User < ApplicationRecord
  # Devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  # 役職管理(独自拡張)
  enum role: {
    staff: 0,
    manager: 1,
    admin: 2
  }

  validates :role, presence: true
  after_initialize :set_default_role, if: :new_record?

  private

  def set_default_role
    self.role ||= :staff
  end
end

使用しているDeviseモジュール

1. :database_authenticatable

# パスワード認証機能
user = User.find_by(email: "[email protected]")
user.valid_password?("password123")  # => true/false

# 内部でbcryptを使用してパスワード検証
BCrypt::Password.new(user.encrypted_password) == "password123"

2. :registerable

# ユーザー登録機能
# Strong Parametersと組み合わせて使用
def user_params
  params.require(:user).permit(:email, :password, :password_confirmation, :role)
end

user = User.new(user_params)
user.save  # Deviseのバリデーションが自動適用

3. :recoverable

# パスワードリセット機能(今回未使用だが利用可能)
user.send_reset_password_instructions
# reset_password_token が生成され、メールが送信される

4. :rememberable

# "ログインを記憶する" 機能(今回未使用だが利用可能)
user.remember_me!
# remember_created_at が更新される

5. :validatable

# 自動バリデーション
# - email presence & uniqueness & format
# - password minimum length (6文字以上)
# - password_confirmation matching

🎯 JWT認証での活用パターン

API Controller での使用例

class Api::V1::AuthController < Api::V1::BaseController
  def login
    # emailでユーザーを検索
    user = User.find_by(email: params[:email])

    # Deviseの valid_password? メソッドを使用
    if user&.valid_password?(params[:password])
      # 認証成功:JWTトークンを生成
      token = JsonWebToken.encode(user_id: user.id)
      
      render json: {
        token: token,
        user: user.as_json(only: [:id, :email, :role]),
        expires_at: 24.hours.from_now
      }, status: :ok
    else
      # 認証失敗:Deviseが提供するセキュアなエラーハンドリング
      render json: { error: 'Invalid credentials' }, status: :unauthorized
    end
  end

  def signup
    # Deviseの機能を活用したユーザー作成
    user = User.new(user_params)

    if user.save  # Deviseのバリデーションが自動実行
      # 登録成功:即座にJWTトークンを発行
      token = JsonWebToken.encode(user_id: user.id)
      
      render json: {
        token: token,
        user: user.as_json(only: [:id, :email, :role]),
        expires_at: 24.hours.from_now
      }, status: :created
    else
      # 登録失敗:Deviseのバリデーションエラーを返却
      render json: {
        errors: user.errors.full_messages
      }, status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation, :role)
  end
end

🔒 セキュリティ機能

1. パスワードハッシュ化

# 入力されたパスワード
password = "my_secret_password"

# Deviseが自動で行うハッシュ化(bcrypt使用)
encrypted = BCrypt::Password.create(password)
# => "$2a$12$XYZ..." (60文字のハッシュ値)

# データベースには暗号化されたパスワードのみ保存
# 平文パスワードは保存されない

2. ブルートフォース攻撃対策

# Deviseの設定(config/initializers/devise.rb)
Devise.setup do |config|
  # パスワード最小長
  config.password_length = 6..128
  
  # メール形式チェック
  config.email_regexp = /\A[^@\s]+@[^@\s]+\z/
  
  # ケースセンシティブ(大文字小文字区別)
  config.case_insensitive_keys = [:email]
  
  # ホワイトスペース除去
  config.strip_whitespace_keys = [:email]
end

3. セッション管理(今回はJWTで代替)

# 通常のDevise使用時
class ApplicationController < ActionController::Base
  before_action :authenticate_user!  # Deviseの認証チェック
  
  # Deviseが提供するヘルパーメソッド
  def current_user
    # セッションベースのユーザー取得
  end
  
  def user_signed_in?
    # ログイン状態チェック
  end
end

🎨 カスタマイズ例

1. 独自バリデーション追加

class User < ApplicationRecord
  devise :database_authenticatable, :registerable, :validatable

  # Deviseのバリデーションに加えて独自バリデーション
  validates :role, presence: true
  validates :email, format: { 
    with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i,
    message: "企業メールアドレスを使用してください"
  }
  
  # パスワード強度チェック
  validate :password_complexity
  
  private
  
  def password_complexity
    return if password.blank?
    
    unless password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
      errors.add :password, "大文字・小文字・数字を含む必要があります"
    end
  end
end

2. カスタムメソッド追加

class User < ApplicationRecord
  devise :database_authenticatable, :registerable, :validatable

  # 権限チェックメソッド
  def can_access_admin_panel?
    admin? || manager?
  end
  
  # ログイン試行回数制限
  def increment_failed_attempts
    self.failed_attempts ||= 0
    self.failed_attempts += 1
    
    if failed_attempts >= 5
      self.locked_at = Time.current
    end
    
    save
  end
  
  # アカウントロック状態チェック
  def access_locked?
    locked_at.present? && locked_at > 30.minutes.ago
  end
end

💡 学習・習得のポイント

Deviseの理解度

  • Deviseの各モジュールの機能と使い分けの理解
  • JWT認証との組み合わせによる柔軟な認証システムの構築
  • セキュリティ要件に応じたDevise設定のカスタマイズ

実装スキル

  • API認証でのDevise活用パターンの習得
  • 既存Deviseアプリへの機能追加・カスタマイズ
  • パスワードセキュリティとバリデーション設計の理解

実務への応用

  • レガシーなセッション認証からJWT認証への移行設計
  • エンタープライズレベルの認証要件への対応
  • セキュリティインシデント防止のための実装パターン

Deviseは「認証の車輪の再発明」を防ぎ、セキュアで実用的な認証システムを素早く構築できる優れたgemです。JWT認証と組み合わせることで、モダンなAPI開発とレガシー支援の両方に対応できる柔軟な認証基盤を構築できます。