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. 01为 Rails 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

参考

常见自定义

记住我
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" }

评论

支持 Markdown