Agent Skills: Rails Authentication Patterns

Authentication patterns for Rails applications. Automatically invoked when working with user login, signup, sessions, password resets, token authentication, has_secure_password, Devise, or auth configuration. Triggers on "authentication", "auth", "login", "signup", "session", "password", "has_secure_password", "Devise", "current_user", "sign_in", "sign_out", "remember me", "password reset", "email confirmation". NOT for authorization/permissions (use controller patterns for Pundit) or API token auth (use rails-api-patterns for JWT).

UncategorizedID: ag0os/rails-dev-plugin/rails-auth-patterns

Install this agent skill to your local

pnpm dlx add-skill https://github.com/ag0os/rails-dev-plugin/tree/HEAD/skills/rails-auth-patterns

Skill Files

Browse the full folder contents for rails-auth-patterns.

Download Skill

Loading file tree…

skills/rails-auth-patterns/SKILL.md

Skill Metadata

Name
rails-auth-patterns
Description
Authentication patterns for Rails applications. Automatically invoked when working with user login, signup, sessions, password resets, token authentication, has_secure_password, Devise, or auth configuration. Triggers on "authentication", "auth", "login", "signup", "session", "password", "has_secure_password", "Devise", "current_user", "sign_in", "sign_out", "remember me", "password reset", "email confirmation". NOT for authorization/permissions (use controller patterns for Pundit) or API token auth (use rails-api-patterns for JWT).

Rails Authentication Patterns

The authentication approach is an orthogonal project fact, not an architecture axis — a native project can use Devise, an extracted project can use built-in auth. Match what the project already has; never suggest replacing one approach with another unless explicitly asked.

See patterns.md for detailed code examples.

Detecting the Auth Approach

Read it from the project-conventions fingerprint (Auth category), or detect directly:

1. grep "devise" Gemfile → Devise
2. grep "has_secure_password" app/models/ → built-in auth
3. Check for app/controllers/sessions_controller.rb → custom auth
4. Rails 8+? → built-in generator available

| Approach | Use When | |----------|----------| | Rails 8 generator | New Rails 8+ projects, full control, no gem dependencies | | has_secure_password (manual) | Simple auth, full control, pre-Rails 8 | | Devise | Multi-feature auth (confirmable, lockable, omniauthable) |

Rails 8 Built-In Auth

What the Generator Creates

bin/rails generate authentication
# Creates: User model, Session model, SessionsController,
# Authentication concern, PasswordsController, PasswordsMailer, migrations

Key Rails 8 Features (Non-Obvious)

generates_token_for with automatic invalidation:

class User < ApplicationRecord
  has_secure_password
  has_many :sessions, dependent: :destroy

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

  # Token auto-invalidates when password_salt changes (i.e., password changed)
  generates_token_for :password_reset, expires_in: 15.minutes do
    password_salt&.last(10)
  end

  # Token auto-invalidates when email changes
  generates_token_for :email_confirmation, expires_in: 24.hours do
    email_address
  end

  # Non-expiring token (e.g., unsubscribe links)
  generates_token_for :unsubscribe
end

# Generate: user.generate_token_for(:password_reset)
# Find:     User.find_by_token_for(:password_reset, token)  # nil if expired/invalid
# Find!:    User.find_by_token_for!(:password_reset, token) # raises if invalid

CurrentAttributes pattern:

# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :session
  delegate :user, to: :session, allow_nil: true
end

Rate limiting (Rails 8 built-in):

class SessionsController < ApplicationController
  allow_unauthenticated_access only: %i[new create]
  rate_limit to: 10, within: 3.minutes, only: :create, with: -> {
    redirect_to new_session_url, alert: "Try again later."
  }
end

class PasswordsController < ApplicationController
  rate_limit to: 5, within: 1.hour, only: :create, with: -> {
    redirect_to new_password_url, alert: "Too many reset requests."
  }
end

Multiple session management:

# Allow users to see/terminate their active sessions
def destroy_all
  Current.user.sessions.where.not(id: Current.session.id).destroy_all
  redirect_to sessions_path, notice: "All other sessions terminated."
end

Devise — Key Customization Points

Follow standard Devise setup/installation docs. The non-obvious parts:

Turbo compatibility (Rails 7+) — required:

# config/initializers/devise.rb
config.responder.error_status = :unprocessable_entity
config.responder.redirect_status = :see_other
config.navigational_formats = ["*/*", :html, :turbo_stream]

Custom failure app for Turbo (if redirects break):

class TurboFailureApp < Devise::FailureApp
  def respond
    if request_format == :turbo_stream
      redirect
    else
      super
    end
  end

  def skip_format?
    %w[html turbo_stream */*].include?(request_format.to_s)
  end
end

# config/initializers/devise.rb
config.warden do |manager|
  manager.failure_app = TurboFailureApp
end

See patterns.md for OmniAuth integration and custom controller patterns.

Test Helpers

Write tests in the project's framework — read it from the project-conventions fingerprint (Testing category). The two examples below pair an auth approach with a framework, but the framework choice is independent of the auth approach.

Built-in auth, Minitest example:

class SessionsControllerTest < ActionDispatch::IntegrationTest
  test "login with valid credentials" do
    user = users(:jane)
    post session_url, params: { email_address: user.email_address, password: "password" }
    assert_redirected_to root_path
  end

  test "rate limiting after 10 attempts" do
    11.times { post session_url, params: { email_address: "x@x.com", password: "wrong" } }
    assert_redirected_to new_session_url
    follow_redirect!
    assert_match "Try again later", flash[:alert]
  end
end

Devise, RSpec example:

# spec/rails_helper.rb
RSpec.configure do |config|
  config.include Devise::Test::IntegrationHelpers, type: :request
  config.include Devise::Test::IntegrationHelpers, type: :system
end

# In specs — use sign_in helper, don't hit the login endpoint
before { sign_in user }

Anti-Patterns

| Anti-Pattern | Problem | Fix | |-------------|---------|-----| | No rate limiting on login | Brute force attacks | rate_limit (Rails 8) or Rack::Attack | | Password reset tokens without expiry | Token reuse attacks | expires_in: 15.minutes | | Leaking user existence on reset | Enumeration attacks | Same response for valid/invalid emails | | Session fixation | Hijacking | reset_session on login | | Rolling your own token system | Crypto bugs | Use generates_token_for or Devise tokens |

Output Format

When implementing auth, provide:

  1. Model with auth configuration
  2. Controller(s) for sessions, registrations, password resets
  3. Routes configuration
  4. Test file matching project profile (Minitest or RSpec)
  5. Security notes (rate limiting, token expiry, session management)
Rails Authentication Patterns Skill | Agent Skills