Rails_ai_agents authentication-flow
install
source · Clone the upstream repo
git clone https://github.com/ThibautBaissac/rails_ai_agents
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ThibautBaissac/rails_ai_agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/authentication-flow" ~/.claude/skills/thibautbaissac-rails-ai-agents-authentication-flow && rm -rf "$T"
manifest:
.claude/skills/authentication-flow/SKILL.mdsource content
Rails 8 Authentication
Overview
Rails 8 includes a built-in authentication generator that creates a complete, secure authentication system without external gems.
Quick Start
# Generate authentication bin/rails generate authentication # Run migrations bin/rails db:migrate
This creates:
model withUserhas_secure_password
model for secure sessionsSession
model for request-local storageCurrent- Authentication concern for controllers
- Session and Password controllers
- Login/logout views
Generated Structure
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
Core Components
User Model
# 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 Model
# 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 Model
# app/models/current.rb class Current < ActiveSupport::CurrentAttributes attribute :session delegate :user, to: :session, allow_nil: true end
Authentication 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
Usage Patterns
Protecting Controllers
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
Accessing Current User
# In controllers Current.user Current.user.email_address # In views <%= Current.user.email_address %> # In models (use sparingly) Current.user
Login Flow
# 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
Testing Authentication
Request Specs
# 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
Test Helper
# 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
Protected Route Specs
# 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
References
- See sessions.md for session management details
- See current.md for Current attributes patterns
Common Customizations
Remember Me
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
Multiple Sessions Tracking
# 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
Rate Limiting
# app/controllers/sessions_controller.rb rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_path, alert: "Too many attempts" }