【Rails】authorizeとは？[Pundit] / 403エラー画面の作成有り

## 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``を記入。

```app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include Pundit
end
```

認可のルールを記述するファイルを作成するために、下記コマンドを入力。

```
$ rails g pudit:install
```

すると``app/policies/application_policy.rb``というファイルが生成される。

```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``を作成｡

```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``が発生する。

## コントローラーでの呼び出す

```app/controller/posts_controller.rb
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``を作成し、下記を記入。

```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``にして､サーバを再起動することで可能になる。

```config/environments/development.rb
# エラー画面404と403をデバッグ用か本番用か切り替えられる
# config.consider_all_requests_local = true
　　　　config.consider_all_requests_local = false
```

確認ができたら``true``に戻しておく。

#### 本番環境で403エラー画面を表示させる
config/application.rbに設定を記述して､サーバを再起動させればOK。

```config/application.rb
#例外を403HTTPステータスにする。これを付けないと500になる｡
# :forbiddenというシンボルはステータスコード403と定義されている｡
config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden
```

``Pundit::NotAuthorizedError``を補足させ､``:forbidden``を指定することで､HTTPステータスコードが403になる｡
このシンボルはL661で定義されている｡

#### 共通レイアウトも表示させる場合
共通レイアウトも表示させる場合は､``ApplicationController``で``rescue``処理を記載して､``app/view``以下のテンプレートファイルを用意して``render``させる。

```rb
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による権限管理 (認可)](https://zenn.dev/yusuke_docha/articles/7b4b2f3f1bb203)
[Pundit + Railsで認可の仕組みをシンプルに作る](https://qiita.com/zaru/items/8bf7b41b33f3f55bd27d)
[Punditをなるべくやさしく解説する](https://qiita.com/yutaro50/items/52484b7ae4ca87aa99a2)
[よくわかる認証と認可](https://dev.classmethod.jp/articles/authentication-and-authorization/)