# Authentication Flow

> Implements authentication using Rails 8 built-in generator. Use when setting up user authentication, login/logout, session management, password reset flows, or securing controllers.

- Skill: `dchuk/authentication-flow` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add dchuk/authentication-flow`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dchuk/authentication-flow/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: dchuk (https://skillmd.com/u/dchuk)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/dchuk/authentication-flow

---


# 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 HomeController < ApplicationController
  allow_unauthenticated_access only: [:index, :about]
end
```

### Accessing Current User

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

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

### Test Helper

```ruby
# test/test_helper.rb
class ActionDispatch::IntegrationTest
  def sign_in(user)
    session = user.sessions.create!
    cookies[:session_token] = session.token
  end

  def sign_out
    cookies.delete(:session_token)
  end
end
```

### Session Controller Tests

```ruby
# test/controllers/sessions_controller_test.rb
require "test_helper"

class SessionsControllerTest < ActionDispatch::IntegrationTest
  setup do
    @user = users(:one)
  end

  test "GET new renders login form" do
    get new_session_path
    assert_response :success
  end

  test "POST create with valid credentials signs in user" do
    post session_path, params: {
      email_address: @user.email_address,
      password: "password123"
    }

    assert_redirected_to root_path
    assert cookies[:session_token].present?
  end

  test "POST create with invalid credentials shows error" do
    post session_path, params: {
      email_address: @user.email_address,
      password: "wrong"
    }

    assert_response :unprocessable_entity
  end

  test "DELETE destroy signs out user" do
    sign_in @user

    delete session_path

    assert_redirected_to root_path
    assert_nil cookies[:session_token]
  end
end
```

### Protected Route Tests

```ruby
# test/controllers/posts_controller_test.rb
require "test_helper"

class PostsControllerTest < ActionDispatch::IntegrationTest
  setup do
    @user = users(:one)
  end

  test "redirects to login when not authenticated" do
    get posts_path
    assert_redirected_to new_session_path
  end

  test "shows posts when authenticated" do
    sign_in @user

    get posts_path
    assert_response :success
  end
end
```

## References

- See [sessions.md](reference/sessions.md) for session management details
- See [current.md](reference/current.md) for Current attributes patterns
- See [passwordless.md](reference/passwordless.md) for magic link authentication

## 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
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" }
```

## Checklist

- [ ] Authentication generator run
- [ ] Test helper with `sign_in`/`sign_out` methods
- [ ] Session controller tests written
- [ ] Protected route tests written
- [ ] Rate limiting on login
- [ ] `allow_unauthenticated_access` on public pages
- [ ] All tests GREEN

