Implements authentication using Rails 8 built-in generator. Use when setting up user authentication, login/logout, session management, password reset flows, or securing controllers.
Rails 8 includes a built-in authentication generator that creates a complete, secure authentication system without external gems.
# Generate authentication
bin/rails generate authentication
# Run migrations
bin/rails db:migrate
This creates:
User model with has_secure_passwordSession model for secure sessionsCurrent model for request-local storageapp/
├── 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
# 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
# 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
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :session
delegate :user, to: :session, allow_nil: true
end
# 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" }
npx skills add rivettidaniel/authentication-flow下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer