mcpField
A
authentication-flow

authentication-flow

github.com/thibautbaissacセキュリティ67157 回インストール21 週間前に更新
リポジトリ

authentication-flow について

Rails 8 開発者向けの認証フローのスキルです。組み込みジェネレーターによるユーザー、セッション、コントローラーの作成に加え、ログイン、パスワードリセット、テストの方法を扱います。

主な機能

  1. 01ユーザーとセッションモデルの生成
  2. 02コントローラーの認証保護
  3. 03ログイン、ログアウト、パスワードリセット
  4. 04セッション管理のカスタマイズ
  5. 05リクエストスペックのテストパターン

活用シーン

  1. 01Rails 8 アプリへの認証追加
  2. 02コントローラーへのアクセス制限
  3. 03ログインとパスワードリセットの実装
  4. 04認証リクエストスペックの作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/thibautbaissac/rails_ai_agents --skill authentication-flow

インストール後、Claude Code で/authentication-flowを入力して読み込まれたか確認します。

同梱ファイルreferences/current.mdreferences/sessions.md

詳細

Rails 8 認証

概要

Rails 8 には、外部 gem を使わずに完全で安全な認証システムを作成する、組み込みの認証ジェネレーターが含まれています。

クイックスタート

# Generate authentication
bin/rails generate authentication

# Run migrations
bin/rails db:migrate

以下が作成されます。

  • has_secure_password を備えた User モデル
  • 安全なセッション用の Session モデル
  • リクエスト単位のストレージ用の Current モデル
  • コントローラー用の認証 concern
  • Session コントローラーと Password コントローラー
  • ログイン/ログアウトのビュー

生成される構成

app/
├── models/
│   ├── user.rb              # User with has_secure_password
│   ├── session.rb           # Session tracking
│   └── current.rb           # Current.user accessor
├── controllers/
│   ├── sessions_controller.rb      # Login/logout
│   ├── passwords_controller.rb     # Password reset
│   └── concerns/
│       └── authentication.rb       # Auth helpers
└── views/
    ├── sessions/
    │   └── new.html.erb     # Login form
    └── passwords/
        ├── new.html.erb     # Forgot password
        └── edit.html.erb    # Reset password

主なコンポーネント

User モデル
# app/models/user.rb
class User < ApplicationRecord
  has_secure_password
  has_many :sessions, dependent: :destroy

  normalizes :email_address, with: -> { _1.strip.downcase }

  validates :email_address, presence: true, uniqueness: true,
            format: { with: URI::MailTo::EMAIL_REGEXP }
end
Session モデル
# app/models/session.rb
class Session < ApplicationRecord
  belongs_to :user

  before_create { self.token = SecureRandom.urlsafe_base64(32) }

  def self.find_by_token(token)
    find_by(token: token) if token.present?
  end
end
Current モデル
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :session
  delegate :user, to: :session, allow_nil: true
end
認証 Concern
# app/controllers/concerns/authentication.rb
module Authentication
  extend ActiveSupport::Concern

  included do
    before_action :require_authentication
    helper_method :authenticated?
  end

  class_methods do
    def allow_unauthenticated_access(**options)
      skip_before_action :require_authentication, **options
    end
  end

  private

  def authenticated?
    Current.session.present?
  end

  def require_authentication
    resume_session || request_authentication
  end

  def resume_session
    if session_token = cookies.signed[:session_token]
      if session = Session.find_by_token(session_token)
        Current.session = session
      end
    end
  end

  def request_authentication
    redirect_to new_session_path
  end

  def start_new_session_for(user)
    session = user.sessions.create!
    cookies.signed.permanent[:session_token] = { value: session.token, httponly: true }
    Current.session = session
  end

  def terminate_session
    Current.session&.destroy
    cookies.delete(:session_token)
  end
end

使用パターン

コントローラーを保護する
class ApplicationController < ActionController::Base
  include Authentication

  # All actions require authentication by default
end

class PostsController < ApplicationController
  # All actions protected
end

class HomeController < ApplicationController
  # Allow public access to specific actions
  allow_unauthenticated_access only: [:index, :about]
end
現在のユーザーにアクセスする
# In controllers
Current.user
Current.user.email_address

# In views
<%= Current.user.email_address %>

# In models (use sparingly)
Current.user
ログインフロー
# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  allow_unauthenticated_access only: [:new, :create]

  def new
  end

  def create
    if user = User.authenticate_by(email_address: params[:email_address],
                                    password: params[:password])
      start_new_session_for(user)
      redirect_to root_path, notice: "Signed in successfully"
    else
      flash.now[:alert] = "Invalid email or password"
      render :new, status: :unprocessable_entity
    end
  end

  def destroy
    terminate_session
    redirect_to root_path, notice: "Signed out"
  end
end

認証のテスト

リクエストスペック
# spec/requests/sessions_spec.rb
RSpec.describe "Sessions", type: :request do
  let(:user) { create(:user, password: "password123") }

  describe "POST /session" do
    context "with valid credentials" do
      it "signs in the user" do
        post session_path, params: {
          email_address: user.email_address,
          password: "password123"
        }

        expect(response).to redirect_to(root_path)
        expect(cookies[:session_token]).to be_present
      end
    end

    context "with invalid credentials" do
      it "shows error" do
        post session_path, params: {
          email_address: user.email_address,
          password: "wrong"
        }

        expect(response).to have_http_status(:unprocessable_entity)
      end
    end
  end

  describe "DELETE /session" do
    it "signs out the user" do
      # First sign in
      post session_path, params: {
        email_address: user.email_address,
        password: "password123"
      }

      delete session_path

      expect(response).to redirect_to(root_path)
    end
  end
end
テストヘルパー
# spec/support/authentication_helpers.rb
module AuthenticationHelpers
  def sign_in(user)
    session = user.sessions.create!
    cookies[:session_token] = session.token
  end

  def sign_out
    cookies.delete(:session_token)
  end
end

RSpec.configure do |config|
  config.include AuthenticationHelpers, type: :request
  config.include AuthenticationHelpers, type: :system
end
保護されたルートのスペック
# spec/requests/posts_spec.rb
RSpec.describe "Posts", type: :request do
  let(:user) { create(:user) }

  describe "GET /posts" do
    context "when not authenticated" do
      it "redirects to login" do
        get posts_path
        expect(response).to redirect_to(new_session_path)
      end
    end

    context "when authenticated" do
      before { sign_in(user) }

      it "shows posts" do
        get posts_path
        expect(response).to have_http_status(:ok)
      end
    end
  end
end

参考資料

  • セッション管理の詳細については、sessions.md を参照してください
  • Current 属性のパターンについては、current.md を参照してください

よくあるカスタマイズ

ログイン状態を保持する
def start_new_session_for(user, remember: false)
  session = user.sessions.create!
  cookie_options = { value: session.token, httponly: true }
  cookie_options[:expires] = 2.weeks.from_now if remember
  cookies.signed.permanent[:session_token] = cookie_options
  Current.session = session
end
複数セッションの追跡
# In User model
def active_sessions
  sessions.where('created_at > ?', 30.days.ago)
end

def terminate_all_sessions_except(current_session)
  sessions.where.not(id: current_session.id).destroy_all
end
レート制限
# app/controllers/sessions_controller.rb
rate_limit to: 10, within: 3.minutes, only: :create,
           with: -> { redirect_to new_session_path, alert: "Too many attempts" }

コメント