authorizeとは
Rubyのgem Pundeit
のコントローラの各アクションでauthorize
リソースオブジェクトである。
Pundit
とは「認可」の仕組みを提供するもの。
ユーザーによってページの表示の許可・拒否をしたり、表示情報の範囲を変えられるgem。
分かりやすいイメージで言うと
・未ログインの場合に、ログインが必要なページをリクエストすると、ログインページへ遷移
・ログイン済みの場合に、許可されていないページをリクエストすると、ルートURLへ遷移、などが挙げられる。
Pundit
はcurrent_user
(ログイン中のユーザー)メソッドを扱うので、sorcery gem
などが「認証」の仕組みありきで、認可は認証に依存している。
認証と認可の違い
認証(Authentication)
通信の相手が誰(何)であるかを確認することが「認証」。
純粋な認証には「リソース」やそれに対する「権限」という概念は無い。
いわゆる「証明書の確認」のようなもの。
私たちの身近な証明書で言うと、マイナンバーカードが該当する。
例えばマイナンバーカードを拝見して、この人は鈴木さんと言うことが確認できました。
鈴木さんというのは確認できても、何かが許されるといった話はない。
相手が誰なのか確認するだけの状態。
認可(Authorization)
とある特定の条件に対して、リソースアクセスの権限をあたえることが「認可」。
純粋な「認可」には、「誰」という考え方は無い。
いわゆる「電車の切符」や「映画チケットの発行」のようなもの。
チケットがあるからといって誰かの身元が明らかになる話とは関係ない。
電車の切符なら乗車の許可があるだけで、誰かというのは関係ない。
認可はそれを持っているだけで何か(リソースへのアクセス)が許可される。
切符があれば電車に乗れて、持っていなければ電車に乗れない。
つまり、ただの許可証の発行だけ。
ただ多くの場合、認証(誰か確認)できないと認可(許可)できないので、認可は認証に依存していると言える。
Punditの仕組み
Pundit
はコントローラの各アクションでauthorize
リソースオブジェクトを呼ぶと、**対象のリソースに対して権限があるかどうかを確認してくれる。**その設定をapp/policies
にあるポリシーファイルで細かく定義できる。
Punditの使用法
Gemfileにgem "pundit"
と記入し、bundle install
を実施。
Pundit
を適用したいコントローラの継承元でinclude
を記入。
class ApplicationController < ActionController::Base
include Pundit
end
認可のルールを記述するファイルを作成するために、下記コマンドを入力。
$ rails g pudit:install
するとapp/policies/application_policy.rb
というファイルが生成される。
class ApplicationPolicy
attr_reader :user, :record #読み取りの属性を定義している
def initialize(user, record)
@user = user
@record = record
end
def index?
false
end
end
このファイルで定義しているApplicationPolicyクラス
を継承して、他のコントローラごとの認可ルールを記述していく。
initialize
で定義されるuser
は、デフォルトでcurrent_user
が引数に割り当てられるようになっている。
第二引数のrecord
は認可をチェックしたいモデルオブジェクト。対応するモデルのインスタンスを手動で割り当て。
これを利用することで、アクセスしているユーザーオブジェクトと、対象のリソースオブジェクトを知ることができる。
認可ファイルを作成
作り方は、モデル名_policy.rb
でファイル作成し、モデル名Policy
でクラス名定義。
今回は例としてPostという名前のモデルに、app/policies/post_policy.rb
を作成。
class PostPolicy < ApplicationPolicy
def create?
# アクセスユーザー権限がadminまたはeditorのときのみ認可
user.admin? || user.editor?
end
end
def アクション名?
で認可ルール(policy)を記述する。この返り値によって、認可するか否かを判断している。
上記のdef create?
でfalse
が返ってきた場合、
PostController
のcreateアクション
は拒否されて、Pundit::NotAuthorizedError
が発生する。
コントローラーでの呼び出す
def create
@tag = Tag.find(params[:id])
authorize(Tag)
# インスタンスモデルを利用しない(policy側ではuserの情報のみ扱う)場合に限り
# authorize Post という記述が可能。
end
・authorize(Tag)
authorizeメソッド
で、先程のpolicyファイル
に記述されたdef create?
が処理される。また引数には対応するモデルオブジェクトを入れる。
Pundit用静的403エラー画面の作成
各認可がfalse
だった場合、Pundit::NotAuthorizedError
が発生してしまうので、エラーを拾って403を返す仕組みを作っておく必要がある。
共通レイアウトは表示させずに、自作のエラー画面public/403.html
を表示する方法
エラー画面のpublic/403.html
を作成し、下記を記入。
<!DOCTYPE html>
<html>
<head>
<title>権限がありません(401)</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<p>権限がありません。</p>
</body>
</html>
なお上記のエラー画面は、本番環境では表示されるが、開発環境ではデフォルトで非表示となっている。開発環境で403エラー画面を表示して確認するためには、config/environments/development.rb
の config.consider_all_requests_local
をfalse
にして、サーバを再起動することで可能になる。
# エラー画面404と403をデバッグ用か本番用か切り替えられる
# config.consider_all_requests_local = true
config.consider_all_requests_local = false
確認ができたらtrue
に戻しておく。
本番環境で403エラー画面を表示させる
config/application.rbに設定を記述して、サーバを再起動させればOK。
#例外を403HTTPステータスにする。これを付けないと500になる。
# :forbiddenというシンボルはステータスコード403と定義されている。
config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden
Pundit::NotAuthorizedError
を補足させ、:forbidden
を指定することで、HTTPステータスコードが403になる。
このシンボルはL661で定義されている。
共通レイアウトも表示させる場合
共通レイアウトも表示させる場合は、ApplicationController
でrescue
処理を記載して、app/view
以下のテンプレートファイルを用意してrender
させる。
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
render file: Rails.root.join('public/403.html'), status: :forbidden
end
end
参考記事
[Rails]gem Punditによる権限管理 (認可)
Pundit + Railsで認可の仕組みをシンプルに作る
Punditをなるべくやさしく解説する
よくわかる認証と認可