1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-03-13

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

RailsアプリケーションでのDevise導入と認証の基本

導入

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

解決策

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

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

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

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

注意点

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

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

認証と認可の組み合わせ

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

DeviseとCanCanCanの組み合わせ

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

複数モデルの認証管理

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

アクセス制御と認可

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

セキュリティの懸念

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

対策

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

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

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

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

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

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

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

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

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

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

マルチモデル認証の対応

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

認証と認可の組み合わせ

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

まとめ

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

サンプルケース

  • Hospitalモデルで管理されるユーザとClinicモデルで管理されるユーザがおり、Hospitalindexビューに関しては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ユーザーのみがアクセスできるように制限します。

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

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

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

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

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

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

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

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

注意事項

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

    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の認証メソッド: ApplicationControllerauthenticate_hospital!を記述すると、全てのアクションでHospitalユーザーのみが許可され、Clinicユーザーはアクセスできなくなります。そのため、HospitalClinicの両方のユーザーにアクセスを許可したい場合は、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を追加して、バンドルインストールします。

gem 'cancancan'

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

rails g cancan:ability

Abilityクラスの定義

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

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を読み取ることができるように設定されています。

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

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

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クラスで権限を定義し、コントローラーでこれらの権限を適用することにより、セキュリティを強化しつつ、開発者の作業負担を軽減することができます。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?