DeviseとCanCanCanを併用したセキュリティ保護

細かいことを飛ばして具体例を見たい場合は以下のリンクへどうぞ（私自身、備忘録的に見返すならばこの部分になると思っています）

https://qiita.com/KM9973/items/0b132f0ac91322939d44#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B1%E3%83%BC%E3%82%B9
# RailsアプリケーションでのDevise導入と認証の基本

### 導入

RailsアプリケーションにDeviseを導入した際、すべてのビューがログインせずともアクセス可能な状態になってしまうことがあります。これは、適切な認証フィルターが設定されていないために発生します。

### 解決策

#### ApplicationControllerでの認証フィルターの設定

アプリケーション全体でユーザーがログインしていることを要求するには、`ApplicationController`に`before_action :authenticate_user!`を設定します。これにより、全てのコントローラーでアクションが実行される前にユーザーがログインしているかどうかをチェックし、未ログインの場合はログインページにリダイレクトさせることが可能になります。

#### 特定のコントローラーでの認証フィルターの設定

一部のビューのみアクセス制御を行いたい場合、特定のコントローラーにのみ認証フィルターを設定します。例えば、`PostsController`では、`before_action :authenticate_user!, except: [:index, :show]`と設定することで、`index`と`show`アクション以外にログインを要求することが可能です。

### 注意点

- `before_action`の設定を忘れると、そのコントローラーのアクションは全て未ログインのユーザーにもアクセス可能になってしまいます。
- ログインページやサインアップページはDeviseを使用する場合、デフォルトで未ログインユーザーでもアクセスできます。これは一般的には期待される動作ですが、アプリケーションによってはさらなるカスタマイズが必要になる場合もあります。

 
# DeviseとCanCanCanによる認証と認可の実装

### 認証と認可の組み合わせ

Railsアプリケーションでユーザー管理を行う際、認証（Authentication）と認可（Authorization）は重要な要素です。`authenticate_user!`はDeviseのメソッドで、ユーザーがログインしているかどうかを確認し、ログインしていない場合はログインページにリダイレクトさせます。一方、CanCanCanはRailsの認可ライブラリで、ユーザーが特定のアクションを実行する権限を持っているかどうかをチェックします。

### DeviseとCanCanCanの組み合わせ

DeviseとCanCanCanを組み合わせて使用することで、認証と認可を効率的に管理できます。通常、Deviseを使用してユーザーの認証を行い、CanCanCanを使用して認可を管理します。つまり、ユーザーが誰であるかを確認した後、そのユーザーが何ができるか（どのリソースにアクセスできるか、どのアクションを実行できるかなど）を制御します。

### 複数モデルの認証管理

Deviseを使用して`Hospital`と`Clinic`のような複数のモデルでユーザー認証を管理する場合、各モデルに対してDeviseを設定し、それぞれのモデルに対応する認証メソッド（例：`authenticate_hospital!`、`authenticate_clinic!`）を使用します。これにより、特定のモデルに関連するアクションにアクセスする前に、適切なモデルのユーザーがログインしているかを確認できます。

### アクセス制御と認可

特定のモデルのユーザーがログインしていない時に他のリソースへのアクセスを制限するには、CanCanCanの`Ability`クラスでアクセスポリシーを定義します。例えば、`Patient`モデルは`Hospital`ユーザーのみが閲覧できるようにするなどの権限設定ができます。

### セキュリティの懸念

`ApplicationController`や他のコントローラーに`authenticate_hospital!`や`authenticate_clinic!`などのDeviseの認証メソッドを記述していない場合、セキュリティ的にリスクがあります。特に、アプリケーションのどの部分にもログインを要求しない状態でCanCanCanのみに依存している場合、未認証のユーザーが特定のエンドポイントにアクセスできる可能性があります。

### 対策

- **認証フィルターの適用**: `ApplicationController`に`before_action :authenticate_user!`、`before_action :authenticate_user!`などを適用し、全てのアクションに対してユーザーのログイン状態を確認します。これにより、アプリケーション全体で統一された認証ポリシーを実装できます。

- **認可の厳格な実装**: CanCanCanで定義された`Ability`クラスを利用して、適切なユーザーのみがリソースにアクセスできるようにします。また、`load_and_authorize_resource`または`authorize!`メソッドを使用して、認可が適切に行われていることを確認します。


# Railsアプリケーションでの認証メソッドの適用と認証フローのカスタマイズ

### アプリケーション全体の認証ポリシーの実装

ログイン画面と新規登録画面を除く、すべてのページでユーザーがログインしていることを要求する場合、`ApplicationController`に`authenticate_user!`を適用することが一般的なアプローチです。これにより、アプリケーション全体で統一された認証ポリシーを簡単に実装でき、セキュリティを強化できます。

```ruby
class ApplicationController < ActionController::Base
  before_action :authenticate_user!
end
```

### 特定のコントローラーやアクションでの認証のカスタマイズ

一方で、アプリケーションの一部のセクションやアクションにのみログインを要求する柔軟性が必要な場合は、個々のコントローラーに`authenticate_user!`を適用し、必要に応じて`skip_before_action`を使用して認証をスキップすることが望ましいです。

たとえば、`HospitalsController`では、`index`アクションは誰でもアクセスできるようにし、`show`アクションはログインしたユーザーのみがアクセスできるように制限したい場合、以下のようにコントローラーを設定します。

```ruby
class HospitalsController < ApplicationController
  before_action :authenticate_user!, only: [:show]
end
```

### マルチモデル認証の対応

複数のユーザーモデル（例えば、`Hospital`、`Clinic`）を扱う場合、`authenticate_hospital!`や`authenticate_clinic!`のように、各モデルに対応する認証メソッドを適用することが重要です。これにより、各モデルに基づくユーザーのログイン状態を適切に管理し、リソースへのアクセスを制御できます。

### 認証と認可の組み合わせ

CanCanCanなどの認可ライブラリを併用することで、認証済みのユーザーに対してさらに細かいアクセス権限を設定することが可能です。Deviseによる認証後、CanCanCanを使用して特定のリソースへのアクセスを制限することで、アプリケーションのセキュリティとユーザーエクスペリエンスの両方を向上させることができます。

### まとめ

- アプリケーション全体に対するデフォルトの認証ポリシーは、`ApplicationController`に認証メソッドを適用することで実装できます。
- 特定のセクションやアクションに対して認証をカスタマイズしたい場合は、個々のコントローラーに認証メソッドを適用し、`skip_before_action`を用いて柔軟に調整します。
- 複数のユーザーモデルを管理する場合は、各モデルに対応した認証メソッドを使用し、適切な認証フローを確保します。
- 認証（Devise）と認可（CanCanCan）を組み合わせることで、アプリケーションのセキュリティを強化し、ユーザーが適切な認証を経て初めてアクセスできるように制御します。


# サンプルケース
- `Hospital`モデルで管理されるユーザと`Clinic`モデルで管理されるユーザがおり、`Hospital`の`index`ビューに関しては`Hospital`ユーザ、`Clinic`ユーザ、ログインしていないユーザがみられるようにする。
- `Hospital`モデルの`show`ビューは`Hospital`ユーザと`Clinic`ユーザのみが見られるようにする。
- `Patient`モデルはHospitalと1対多の関係である。
- `Hospitalモデルは`hospital_id`が一致する`Patient`モデルのみ閲覧できる。
- `Clinic`モデルは`Patient`モデルを閲覧できない。

### [補足]ユーザがHospitalモデルしかいない場合
application_controllerに`before_action: authenticate_hospital!`と記述し、`hospitals_controller`でindexアクションのみをskip_before_actionにすればよい。

### サンプルケースの場合
- ユーザが複数モデルがいるので上記のような実装が適していない可能性がある。
- たいていの場合はDeviseとCanCanCanを使うのがベター（と現時点では思っています）。

以下で、DeviseとCanCanCanを使用するケースと使用しないケースの両方を考えます。まずは、CanCanCanを使用しないシンプルな認証のみのアプローチから説明します。

## （あえて）CanCanCanを使用しない実装

このアプローチでは、Deviseの認証機能とRailsのコントローラーのコールバックを利用して、アクセス制御を実装します。

### `ApplicationController`の設定

`ApplicationController`には特定の認証メソッドを記述しません。これは、アプリケーション全体で一律の認証ポリシーを適用せず、特定のビューやアクションに対して柔軟にアクセス制御を行いたいためです。

### `HospitalsController`の設定

`HospitalsController`では、`index`アクションを全ユーザーがアクセス可能にし、`show`アクションに対してはログインした`Hospital`ユーザーと`Clinic`ユーザーのみがアクセスできるように制限します。

```ruby
class HospitalsController < ApplicationController
  before_action :authenticate_user!, only: [:show]

  def index
    # 全ユーザーがアクセス可能
  end

  def show
    # HospitalユーザーとClinicユーザーのみがアクセス可能
  end
end
```

ここで、`authenticate_user!`は`Hospital`と`Clinic`の両方のユーザーを認証する汎用的なメソッドとして定義する必要があります。Deviseはデフォルトでモデルごとの認証メソッド（例：`authenticate_hospital!`）を提供しますが、複数モデルの認証を組み合わせる場合は、カスタムの認証メソッドを`ApplicationController`に定義することで対応可能です。

### カスタム認証メソッドの定義

`ApplicationController`内に、`Hospital`ユーザーまたは`Clinic`ユーザーがログインしているかをチェックするカスタム認証メソッドを定義します。

```ruby
class ApplicationController < ActionController::Base
  def authenticate_user! # hospitals_controllerで使用
    unless hospital_signed_in? || clinic_signed_in?
      redirect_to new_user_session_path
    end
  end
end
```

ここで、`hospital_signed_in?`と`clinic_signed_in?`はDeviseが提供するヘルパーメソッドです。これらのメソッドを使用して、`Hospital`または`Clinic`のいずれかのユーザーがログインしているかを判断します。

### Patientsへのアクセス制御

`Patients`モデルへのアクセス制御には、`HospitalsController`や`PatientsController`内で、`Hospital`の`id`が`Patient`に関連付けられているかどうかをチェックするロジックを実装する必要があります。これは、`show`アクションやその他の関連アクションで、対象の`Patient`が現在ログインしている`Hospital`に属しているかを確認することで実現できます。

この方法では、アプリケーションの認証ロジックが分散し、特定のシナリオに対して細かく調整することが可能ですが、複雑なアクセス制御や認可ロジックを実装する際には、管理が煩雑になるおそれがあります。

### 注意事項

1. **`Hospital`に関連する`Patients`へのアクセス**: `Hospital`ユーザは`hospital_id`の一致する`Patients`のみ閲覧可能にするという要件ですが、単純に`authenticate_hospital!`を使用するだけでは実現できません（他のHospitalユーザのPatientにアクセスできてしまいます。逆に、他のHospitalユーザからアクセスされてしまいます）。
そのため、各`Patient`が現在ログインしている`Hospital`に関連付けられているかどうかをチェックする追加のロジックが必要です。

    ```ruby
    class PatientsController < ApplicationController
      before_action :authenticate_hospital!, only: [:show, :edit, :update, :destroy]
    
      # showアクション内で、Patientが現在ログインしているHospitalに属しているかをチェック
      def show
        @patient = Patient.find(params[:id])
        unless @patient.hospital_id == current_hospital.id
          redirect_to root_path, alert: 'Access denied.'
        end
      end
    end
    ```
    上記の方法でも基本的な認証とアクセス制御の要件を満たすことは可能ですが、認可のロジックが複雑になるケースでは、CanCanCanのような認可ライブラリを使用することで、より効率的かつ明確に権限管理を行うことができます。

2. **`ApplicationController`の認証メソッド**: `ApplicationController`に`authenticate_hospital!`を記述すると、全てのアクションで`Hospital`ユーザーのみが許可され、`Clinic`ユーザーはアクセスできなくなります。そのため、`Hospital`と`Clinic`の両方のユーザーにアクセスを許可したい場合は、`authenticate_hospital_or_clinic!`のようなカスタム認証メソッドを`ApplicationController`に記述することが適切です。

3. **モデル固有のコントローラーでの認証**: 特定のモデル（例えば`Patients`）にアクセスする際に`Hospital`ユーザーのみを許可したい場合、そのモデルのコントローラー（例：`PatientsController`）に`before_action :authenticate_hospital!`を適用するのが正しいアプローチです。

4. **アクセス制御と認可の複雑さ**: 仮に`Hospital`ユーザーのみが特定のデータにアクセスできるようにした場合でも、`Hospital`ユーザーが他の`Hospital`ユーザーのデータにアクセスしたり操作したりできる可能性があるという点は正確です。これを防ぐためには、各アクション内で`hospital_id`を用いたチェックロジックを実装する必要があります。このロジックは、現在ログインしているユーザーが対象のリソースにアクセスする権限を持っているかどうかを確認するものです。確かに、このようなチェックは煩雑になりがちで、誤りが発生しやすいため、CanCanCanのような認可ライブラリを使用することで、これらの認可ロジックをより簡潔かつ効率的に管理できるようになります。

## CanCanCanを使用する実装

CanCanCanを使用したパターンでは、より洗練されたアクセス制御と認可のロジックを実装できます。

### CanCanCanのセットアップ

まず、Gemfileに`cancancan`を追加して、バンドルインストールします。

```ruby
gem 'cancancan'
```

その後、`Ability`クラスを生成します。これは、ユーザーの権限を定義する場所です。

```bash
rails g cancan:ability
```

### Abilityクラスの定義

`Ability`クラス内で、`Hospital`、`Clinic`、`Patient`に関するアクセス権限を定義します。`current_user`のロールや状態に応じて、リソースへのアクセスを許可または拒否します。

```ruby
class Ability
  include CanCan::Ability

  def initialize(user)
    # userがnilの場合（ログインしていない）、ゲストユーザーとして扱う
    user ||= User.new 

    if user.is_a?(Hospital)
      can :read, Hospital
      can :read, Patient, hospital_id: user.id
      can :manage, Patient, hospital_id: user.id
    elsif user.is_a?(Clinic)
      can :read, Hospital
    end

    # すべてのユーザー（ログインしている/していないに関わらず）に対するアクセス権
    can :read, Hospital, public: true
  end
end
```

この例では、`Hospital`ユーザーは自身に関連する`Patient`に対して読み取りおよび管理（`manage`には読み取り、作成、更新、削除が含まれる）のアクセス権を持ち、`Clinic`ユーザーはすべての`Hospital`を読み取ることができます。また、全てのユーザーは公開されている`Hospital`を読み取ることができるように設定されています。

### コントローラーでの権限チェック

次に、`HospitalsController`と`PatientsController`で、CanCanCanの`load_and_authorize_resource`メソッドを使用して、アクションが実行される前にリソースへのアクセス権限をチェックします。

```ruby
class HospitalsController < ApplicationController
  load_and_authorize_resource

  def index
    # indexアクションのコード
  end

  def show
    # showアクションのコード
  end
end

class PatientsController < ApplicationController
  load_and_authorize_resource

  def show
    # showアクションのコード
  end
end
```

`load_and_authorize_resource`は、コントローラーのアクションに適用される前に、現在のユーザーがそのアクションを実行するための適切な権限を持っているかどうかを自動的にチェックします。権限がない場合は、アクセス拒否の例外が発生します。

### PatientsControllerにbefore_action :authenticate_hospital!を記述
PatientsControllerにauthenticate_hospital!を記述することで、次のようなメリットがあります：

セキュリティの強化: CanCanCanによる権限チェック前に、ユーザーがHospitalとしてログインしているかを確認することで、HospitalユーザーのみがPatientsモデルに関連するアクションにアクセスできるようになります。これは、不正アクセスを防ぐ追加のセキュリティレイヤーとなります。

明確な責任の分離: 認証（Deviseが担当）と認可（CanCanCanが担当）は、異なるセキュリティ概念です。これらを分離して適用することで、コードの可読性と保守性が向上します。

エラーハンドリングの簡素化: ユーザーが未ログインの状態でPatientsにアクセスしようとした場合、authenticate_hospital!により自動的にログインページにリダイレクトされます。これにより、認証されていないアクセスを試みた場合のユーザーエクスペリエンスが向上します。

## まとめ

CanCanCanを使用することで、Railsアプリケーション内での複雑なアクセス制御と認可ロジックを簡潔に実装することが可能になります。`Ability`クラスで権限を定義し、コントローラーでこれらの権限を適用することにより、セキュリティを強化しつつ、開発者の作業負担を軽減することができます。