home / skills / thibautbaissac / rails_ai_agents / authentication-flow

authentication-flow skill

/skills/authentication-flow

This skill helps you implement Rails 8 built-in authentication with a complete user, session, and password reset flow, including controllers and views.

npx playbooks add skill thibautbaissac/rails_ai_agents --skill authentication-flow

Review the files below or copy the command above to add this skill to your agents.

Files (3)
SKILL.md
7.5 KB
---
name: authentication-flow
description: Implements authentication using Rails 8 built-in generator. Use when setting up user authentication, login/logout, session management, password reset flows, or securing controllers.
allowed-tools: Read, Write, Edit, Bash
---

# Rails 8 Authentication

## Overview

Rails 8 includes a built-in authentication generator that creates a complete, secure authentication system without external gems.

## Quick Start

```bash
# Generate authentication
bin/rails generate authentication

# Run migrations
bin/rails db:migrate
```

This creates:
- `User` model with `has_secure_password`
- `Session` model for secure sessions
- `Current` model for request-local storage
- 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

```ruby
# 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

```ruby
# 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

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

### Authentication Concern

```ruby
# 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

```ruby
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

```ruby
# In controllers
Current.user
Current.user.email_address

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

# In models (use sparingly)
Current.user
```

### Login Flow

```ruby
# 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

```ruby
# 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

```ruby
# 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

```ruby
# 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](reference/sessions.md) for session management details
- See [current.md](reference/current.md) for Current attributes patterns

## Common Customizations

### Remember Me

```ruby
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

```ruby
# 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

```ruby
# app/controllers/sessions_controller.rb
rate_limit to: 10, within: 3.minutes, only: :create,
           with: -> { redirect_to new_session_path, alert: "Too many attempts" }
```

Overview

This skill implements Rails 8 built-in authentication generator and supplies a complete, production-ready auth flow without external gems. It scaffolds User, Session, and Current models, controller concerns, and views for login, logout, and password resets. Use it to quickly add secure session management, cookie-based authentication, and easy controller protection.

How this skill works

Run the generator to create models (User, Session, Current), controllers (Sessions, Passwords), and an authentication concern included into ApplicationController. The concern resumes sessions from signed cookies, enforces authentication by default, and provides helpers to start and terminate sessions. The User uses has_secure_password, Session generates secure tokens, and Current exposes request-local user access.

When to use it

  • When adding sign-up, sign-in, and sign-out flows to a Rails 8 app
  • When you need secure, cookie-based session handling without external gems
  • When protecting controllers and actions by default with opt-out for public pages
  • When implementing password reset flows and session tracking
  • When you want test helpers and request specs for auth scenarios

Best practices

  • Include the Authentication concern in ApplicationController to protect all actions by default
  • Use Current.user in controllers and views for request-scoped access; avoid heavy use in models
  • Store session tokens in signed, HttpOnly cookies and rotate or expire tokens for security
  • Write request specs for login, logout, and protected routes; use provided test helpers to sign_in/sign_out
  • Consider rate limiting the sessions#create action to protect against credential stuffing

Example use cases

  • Protecting an admin area so only authenticated users can access PostsController
  • Adding a ‘Remember me’ option by setting cookie expiry when starting a session
  • Implementing multi-device session tracking and an interface to terminate sessions
  • Building password reset flows using the generated PasswordsController and views
  • Writing RSpec request specs that assert redirects for unauthenticated access and successful sign-in flows

FAQ

How does the session token get stored and resumed?

The generator stores a session token in a signed, HttpOnly cookie. On each request the Authentication concern checks cookies.signed[:session_token], looks up the Session by token, and sets Current.session if found.

Can I allow public access to specific controller actions?

Yes. The Authentication concern provides allow_unauthenticated_access which calls skip_before_action for listed actions so those routes remain public.