Rails8で追加された認証ジェネレータを試してみた

Rails8から新しく認証ジェネレータが追加されました。このジェネレータは、認証周りのシステムを自動で生成してくれるもので、基本的な機能がかなり充実しています。

**自動で生成される処理**
- セッション管理
- 認証処理
- パスワードリマインダー

※上記機能に紐づくRoutingやModelの追加も自動で行われます。

今回は、この認証ジェネレータを実際に試してみた内容をまとめました。
認証ジェネレータの詳しい情報が知りたい方は[Rails 8で基本的な認証ジェネレータが導入される（翻訳）](https://techracho.bpsinc.jp/hachi8833/2024_10_21/145343)をご覧ください。

# 環境
- rails8.0.1
- ruby3.2.6

通常のRails環境を準備したのち、以下の手順で実行しました。
1. `rails generate authentication`コマンドを実行
2. `rails db:migrate`コマンドを実行
3. サーバーを起動

# 生成されたページを見てみる
生成されたViewはとても簡素で、最低限のフォームとルーティングが用意されています。
## ログイン画面
`/session/new`にアクセスすると、以下のようなログインページが表示されます。
![スクリーンショット 2024-12-23 23.55.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/5b75737b-f512-6825-2c39-1760650341fc.png)

:::note warn
**ユーザー作成について**
デフォルトでは、新規アカウント作成フローは提供されていません。ユーザー作成が必要な場合は、自力で実装する必要があります。  
今回はコード上で仮のユーザーを作成し、その情報を使ってログインしました。
:::

## パスワードリマインダー
`/passwords/new`にアクセスすことで、パスワードリマインダーのページにアクセスできます。
![スクリーンショット 2024-12-24 0.32.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/e36f39b1-0871-c3f5-711f-2ea6e84f54fd.png)
フォームSubmit処理にはMailerが利用されています。ローカルで試す場合は`mailhog`や`letter_opener`などのツールを導入する必要があるでしょう。

# コードを見て印象的だったこと
## 1. 各種コントローラーでの認証チェック方法
`ApplicationController`に`Authentication`モジュールがincludeされており、このコントローラーを継承した他のコントローラーでは、認証チェックが自動的に行われます。
そのため、追加の実装なしで認証済みユーザー専用のアプリケーションを開発できます。
``` application_controller.rb
class ApplicationController < ActionController::Base
  include Authentication
  # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
  allow_browser versions: :modern
end
```
#### 認証をスキップする方法
特定のアクションで認証をスキップしたい場合は、以下のように記述できます。
``` ruby
allow_unauthenticated_access only: [:index]
```

## 2. アクセス時のURLがログイン後に引き継がれる
未ログイン状態でユーザーがアクセスした場合、セッションにアクセス元のURLが保存されます。
ログイン成功時には保存されたパスにリダイレクトされる仕組みとなっています。
``` authentication.rb
  def request_authentication
    session[:return_to_after_authenticating] = request.url
    redirect_to new_session_path
  end
  ...

  def after_authentication_url
    session.delete(:return_to_after_authenticating) || root_url
  end
```
``` sessions_controller.rb
  def create
    if user = User.authenticate_by(params.permit(:email_address, :password))
      start_new_session_for user
      redirect_to after_authentication_url
    else
      redirect_to new_session_path, alert: "Try another email address or password."
    end
  end
```

## 3. 認証ロジックのまとめ方
ジェネレータが生成する`concerns/authentication.rb`には、認証に必要なロジックがスマートにまとめられています。
``` 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?
      resume_session
    end

    def require_authentication
      resume_session || request_authentication
    end

    def resume_session
      Current.session ||= find_session_by_cookie
    end

    def find_session_by_cookie
      Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]
    end

    def request_authentication
      session[:return_to_after_authenticating] = request.url
      redirect_to new_session_path
    end

    def after_authentication_url
      session.delete(:return_to_after_authenticating) || root_url
    end

    def start_new_session_for(user)
      user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
        Current.session = session
        cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }
      end
    end

    def terminate_session
      Current.session.destroy
      cookies.delete(:session_id)
    end
end
```
コードは読みやすく整理されており、認証の基本を学ぶ良い参考資料になると感じました。

# おわりに
認証ジェネレータは、認証に必要な基本的な機能をシンプルに実装できる便利なツールです。
実際のプロジェクトではそのまま適用するのが難しい場合もあるかもしれませんが、生成されるコードはリファクタリングや構造設計の参考になります。

ブラックボックスな部分が増えてしまうGemとは異なり、独自でカスタマイズしやすいのもジェネレータのいい点だと思います。
認証機能に初めて触れる方や、新しいプロジェクトで試してみたい方は、ぜひ利用してみてください！