細かいことを飛ばして具体例を見たい場合は以下のリンクへどうぞ(私自身、備忘録的に見返すならばこの部分になると思っています)
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!
を適用することが一般的なアプローチです。これにより、アプリケーション全体で統一された認証ポリシーを簡単に実装でき、セキュリティを強化できます。
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
マルチモデル認証の対応
複数のユーザーモデル(例えば、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
ユーザーのみがアクセスできるように制限します。
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
ユーザーがログインしているかをチェックするカスタム認証メソッドを定義します。
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
に属しているかを確認することで実現できます。
この方法では、アプリケーションの認証ロジックが分散し、特定のシナリオに対して細かく調整することが可能ですが、複雑なアクセス制御や認可ロジックを実装する際には、管理が煩雑になるおそれがあります。
注意事項
-
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のような認可ライブラリを使用することで、より効率的かつ明確に権限管理を行うことができます。
-
ApplicationController
の認証メソッド:ApplicationController
にauthenticate_hospital!
を記述すると、全てのアクションでHospital
ユーザーのみが許可され、Clinic
ユーザーはアクセスできなくなります。そのため、Hospital
とClinic
の両方のユーザーにアクセスを許可したい場合は、authenticate_hospital_or_clinic!
のようなカスタム認証メソッドをApplicationController
に記述することが適切です。 -
モデル固有のコントローラーでの認証: 特定のモデル(例えば
Patients
)にアクセスする際にHospital
ユーザーのみを許可したい場合、そのモデルのコントローラー(例:PatientsController
)にbefore_action :authenticate_hospital!
を適用するのが正しいアプローチです。 -
アクセス制御と認可の複雑さ: 仮に
Hospital
ユーザーのみが特定のデータにアクセスできるようにした場合でも、Hospital
ユーザーが他のHospital
ユーザーのデータにアクセスしたり操作したりできる可能性があるという点は正確です。これを防ぐためには、各アクション内でhospital_id
を用いたチェックロジックを実装する必要があります。このロジックは、現在ログインしているユーザーが対象のリソースにアクセスする権限を持っているかどうかを確認するものです。確かに、このようなチェックは煩雑になりがちで、誤りが発生しやすいため、CanCanCanのような認可ライブラリを使用することで、これらの認可ロジックをより簡潔かつ効率的に管理できるようになります。
CanCanCanを使用する実装
CanCanCanを使用したパターンでは、より洗練されたアクセス制御と認可のロジックを実装できます。
CanCanCanのセットアップ
まず、Gemfileにcancancan
を追加して、バンドルインストールします。
gem 'cancancan'
その後、Ability
クラスを生成します。これは、ユーザーの権限を定義する場所です。
rails g cancan:ability
Abilityクラスの定義
Ability
クラス内で、Hospital
、Clinic
、Patient
に関するアクセス権限を定義します。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
を読み取ることができるように設定されています。
コントローラーでの権限チェック
次に、HospitalsController
とPatientsController
で、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
クラスで権限を定義し、コントローラーでこれらの権限を適用することにより、セキュリティを強化しつつ、開発者の作業負担を軽減することができます。