---
title: "RR008 – Enforce Authentication with before_action"
impact: high
impactDescription: "Missing before_action :authenticate_user! leaves actions publicly accessible by default — opt-out is safer than opt-in."
tags: [ruby, rails, security, authentication]
---

# RR008 – Enforce Authentication with before_action

## Rule

Place `before_action :authenticate_user!` in `ApplicationController`. Use `skip_before_action` only for explicitly public endpoints. Never rely on per-action checks or assume private routes are protected.

## Why

If authentication is applied per-action (opt-in), a new action will be public by default — a common source of auth bypass vulnerabilities. Opt-out (skip for public endpoints) is the safe default.

## Wrong

```ruby
# Opt-in per controller — new actions are public by default
class PostsController < ApplicationController
  def index
    authenticate_user!   # ❌ developer must remember to call this
    @posts = Post.all
  end

  def create
    # ❌ forgot authenticate_user! — publicly writable
    Post.create!(post_params)
  end
end

# ApplicationController with no global hook
class ApplicationController < ActionController::API
  # nothing — every action is public until protected
end
```

## Correct

```ruby
# ApplicationController — everything requires auth
class ApplicationController < ActionController::API
  include Devise::Controllers::Helpers   # or your auth layer
  before_action :authenticate_user!

  private

  def current_user_id
    current_user.id
  end
end

# Public endpoints explicitly opt out
class SessionsController < ApplicationController
  skip_before_action :authenticate_user!, only: [:create, :destroy]
  
  def create
    # login — intentionally public
  end
end

class HealthController < ApplicationController
  skip_before_action :authenticate_user!
  
  def show
    render json: { status: 'ok' }
  end
end
```

## Notes

- For Devise APIs, use `authenticate_user!` from `Devise::Controllers::Helpers` or the Devise-JWT extension.
- Documenting `skip_before_action` with a comment explaining WHY the action is public improves auditability.
- Add an integration test that requests every route without auth and asserts `status: 401` for protected ones.
