【Rails】ユーザー認証と認可の基本

# はじめに

こんにちは！
新卒１年目の石川です。
記事をご覧いただきありがとうございます。

この記事では、ユーザー認証と認可の基本を説明します。
Webアプリケーション開発において、ユーザー認証（authentication）と認可（authorization）は非常に重要なセキュリティ機能なので整理してみました。
ぜひ最後までご覧ください！

:::note warn
今回は主に機能の流れを説明します。ロジックの主要部分がコントローラとヘルパーに集中しているため、モデルとビューについてこの記事では触れません。
:::


## 目次
1. [ユーザー認証 (Authentication)](#1-ユーザー認証-authentication)
・[ユーザー登録](#ユーザー登録)
・[ログイン/ログアウト](#ログインログアウト) 

2. [ユーザー認可 (Authorization)](#2-ユーザー認可-authorization)
・[アクセス制御](#アクセス制御)
・[フレンドリーフォワーディング](#フレンドリーフォワーディング)
・[フレンドリーフォワーディングのテスト](#フレンドリーフォワーディングのテスト)
3. [最後に](#3-最後に)




# 1. ユーザー認証 (Authentication)

ユーザー認証とは、ユーザーが誰であるかを確認することです。
今回は、ユーザー認証を「ユーザー登録」「ログイン/ログアウト」の２つの機能に分けて見ていきます！

:::note info
ユーザー認証では、ユーザーがログインするために提供する情報（ユーザー名とパスワードなど）を使用する。
:::

## ユーザー登録

まずは、新規ユーザーを登録する機能を実装します。以下のコードは、ユーザー登録フォームを表示し、ユーザー情報をデータベースに保存するためのものです。ユーザー登録は、アプリケーションに新しいユーザーを追加するプロセスです。ユーザーは先ほども述べた通り、名前、メールアドレス、パスワードなどの情報を入力してアカウントを作成します。




```ruby:app/controllers/users_controller.rb
  class UsersController < ApplicationController

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      reset_session 
      log_in @user   
      flash[:success] = "Welcome to the Sample App!" 
      redirect_to @user 
    else
      render 'new', status: :unprocessable_entity  
    end
  end
  
  private

  def user_params
    params.require(:user).permit(:name, :email, :password, :password_confirmation)
  end
end

```

#### 『newアクション』
新しいユーザーオブジェクトを作成し、登録フォームを表示します。

#### 『createアクション』
フォームデータを受け取り、新しいユーザーを保存します。保存に成功すれば、セッションをリセットしてログインし、プロフィールページにリダイレクトします。失敗した場合はフォームを再表示します。

#### 『user_paramsメソッド』
ユーザーの name、email、password、password_confirmation パラメータを許可します。（private以降の記述なのでプライベートメソッドとして定義されています。これにより、外部からこのメソッドにアクセスすることができず、意図しない使い方を防ぎます。）


## ログイン/ログアウト
次にユーザーがログイン・ログアウト機能を実装します。以下のコードは、セッションコントローラでログイン状態を管理する方法を示しています。ログインは、既存のユーザーがアプリケーションにアクセスできるようにするプロセスです。ログアウトは、ユーザーがアプリケーションから安全に退出するプロセスです。


```ruby:app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new', status: :unprocessable_entity
    end
  end

  def destroy
    log_out
    redirect_to root_url, status: :see_other
  end
end
```

#### 『newアクション』
ログインフォームを表示します。

#### 『createアクション』
フォームデータを受け取り、メールアドレスでユーザーを検索し、パスワードを認証します。認証に成功すればユーザーをログインさせてユーザープロフィールページにリダイレクトします。失敗した場合はエラーメッセージを表示してログインフォームを再表示します。

#### 『destroyアクション』
現在のユーザーをログアウトさせ、ホームページにリダイレクトします。

# 2. ユーザー認可 (Authorization)
ユーザー認可は、ユーザーがどの操作を実行できるか、どのリソースにアクセスできるかを制御する仕組みです。これにより、ユーザーが許可された操作だけを実行できるようにし、セキュリティを確保します。
今回は、ユーザー認可を「アクセス制御」「フレンドリーフォワーディング」「フレンドリーフォワーディングのテスト」の３つの機能に分けて見ていきます！

## アクセス制御
ユーザーが自分自身の情報のみを編集できるようにアクセス制御を実装します。ログインしていないユーザーが特定のアクションにアクセスできないようにします。


```ruby:app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]
  before_action :correct_user, only: [:edit, :update]

  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    if @user.update(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit', status: :unprocessable_entity
    end
  end

  private

    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url, status: :see_other
      end
    end

    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url, status: :see_other) unless current_user?(@user)
    end

    def user_params
      params.require(:user).permit(:name, :email, :password, :password_confirmation)
    end
end
```


#### 『logged_in_userメソッド』
ログインしていないユーザーをログインページにリダイレクトします。
#### 『correct_userメソッド』
現在のユーザーが正しいユーザーであるかを確認し、他のユーザーの編集を防ぎます。

#### 『editアクション』
ユーザーのプロフィール編集フォームを表示します。

#### 『updateアクション』
フォームから送信されたデータを使用してユーザーの情報を更新します。更新が成功すればプロフィールページにリダイレクトし、失敗すれば編集フォームを再表示します。

#### 『user_paramsメソッド』
許可されたパラメータのみを受け取るためのストロングパラメータを定義します。

## フレンドリーフォワーディング

フレンドリーフォワーディングは、ログイン後にユーザーを元の目的地にリダイレクトする機能です。これにより、ユーザーは希望したページにスムーズにアクセスできるようになります。


```ruby:app/helpers/sessions_helper.rb
module SessionsHelper
  def store_location
    session[:forwarding_url] = request.original_url if request.get?
  end

  def redirect_back_or(default)
    redirect_to(session[:forwarding_url] || default)
    session.delete(:forwarding_url)
  end
end
```

#### 『store_locationメソッド』
アクセスしようとしたURLを保存します。
#### 『redirect_back_orメソッド』
保存されたURLにリダイレクトし、保存されたURLがない場合はデフォルトのURLにリダイレクトします。


```ruby:app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      forwarding_url = session[:forwarding_url]
      reset_session
      log_in user
      redirect_to forwarding_url || user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new', status: :unprocessable_entity
    end
  end
end
```
#### 『createアクション』
ユーザーのログインを処理します。ログインが成功した場合、forwarding_urlにリダイレクトし、保存されたURLがない場合はユーザーのプロフィールページにリダイレクトします。ログインに失敗した場合はエラーメッセージを表示し、ログインページを再表示します。



## フレンドリーフォワーディングのテスト

フレンドリーフォワーディング機能が正しく動作するかどうかをテストします。

```ruby:test/integration/users_edit_test.rb
require "test_helper"

class UsersEditTest < ActionDispatch::IntegrationTest
  def setup
    @user = users(:michael)
  end

  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    log_in_as(@user)
    assert_redirected_to edit_user_url(@user)
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end
end
```

#### 『setupメソッド』
テスト用のユーザーをセットアップします。

#### 『テスト』
テストケース"successful edit with friendly forwarding"のフレンドリーフォワーディングをテストします。


## 3. 最後に
この記事ではユーザー認証と認可の重要性、そしてその実装方法について説明しました。ユーザー認証は、誰がアプリケーションにアクセスしているかを確認するプロセスであり、認可はそのユーザーがどの操作を行えるかを制御するプロセスです。これらの機能を適切に実装することで、アプリケーションのセキュリティを強化することができると思います！

この記事で具体的な実装方法がわかりやすくなれば幸いです。また、より安全で使いやすいアプリケーションを作り上げるために、この記事が少しでもお役に立てれば嬉しいです。

最後まで読んでいただきありがとうございました！