Webの管理画面など、ユーザー毎にアクセスできるエンドポイントを制限(認可)したいということがあります。
ElixirのWebアプリケーションフレームワークであるPhoenixは、バージョンによってディレクトリ構造がガラッと変わるなど、バージョンアップで比較的大きな変更があるため、認可のためのライブラリはすぐには見つかりません。
そこでこのエントリでは、Phoenixのプラグ機能を使ってエンドポイント毎の認可を実装する方法を説明します。なお、Phoenixのバージョンは1.4系を想定しています。
Phoenixでエンドポイント毎の認可制御を行うためにはPlug
を作成するのがおそらくもっとも簡単です。
Phoenixでは自作したPlug
を各モジュールで読み込んだり、ルーティングのパイプラインとして仕込むことができます。
エンドポイント毎の認可を考えたとき、Controller plugsとRouter plugsの2通りの方法が考えられますが、今回はController plugsとして作成していきます。
認可を行うMyApp.Plug.EnsureAuthorized
を下記のように定義します。
defmodule MyApp.Plug.EnsureAuthorized do
@moduledoc """
エンドポイント毎のアクセス認可を行うPlug
"""
def init(default), do: default
@spec call(Plug.Conn.t(), any()) :: Plug.Conn.t() | any()
def call(conn, _params) do
controller = conn |> Phoenix.Controller.controller_module() |> to_string()
action = conn |> Phoenix.Controller.action_name() |> Atom.to_string()
conn
|> MyApp.Guardian.Plug.current_resource()
|> authorized?(controller, action)
|> case do
true -> conn
_ -> conn |> MyApp.FallbackController.call({:error, :forbidden})
end
end
defp authorized?(user, controller, action) do
# Check authorization
end
end
Controller plugsでは、Phoenix.Controller.controller_module/1
でルーティングされた現在のControllerモジュールを取得できます。また、アクション名はPhoenix.Controller.action_name/1
で取得できます。どちらもAtomであることに注意してください。
- https://hexdocs.pm/phoenix/Phoenix.Controller.html#controller_module/1
- https://hexdocs.pm/phoenix/Phoenix.Controller.html#action_name/1
認可を行うということはその前段で認証しており「ユーザーが誰であるか(もしくは認証されていないユーザーか)」がわかっていると思います。上記のサンプルでは認証にGuardianを使用しています。
現在のユーザーが、アクセスしているController, actionのペアに対して認可されているかをauthorized?/3
でチェックしてください。
認可されていない場合はFallbackControllerに処理を任せるのが良いでしょう。FallbackController (Action Fallback) については下記を参照してください。
エンドポイント毎の認可を実装するには、前提としてエンドポイント(Controllerおよびaction)が全てわかっている必要があります。
Controllerとactionの全てのペアはMyApp.Router.__routes__
から下記のように取得します。
@spec all_endpoints() :: list(%{controller: String.t(), action: String.t()})
def all_endpoints() do
endpoints_from_router =
MyApp.Router.__routes__()
|> Enum.map(fn route ->
%{controller: "#{route.plug}", action: route.plug_opts |> Atom.to_string()}
end)
endpoints_from_router
|> Enum.uniq_by(&(&1[:controller] <> "." <> &1[:action]))
end
Phoenix.Router.resources
を使ってルーティングを生成している場合、更新のupdate
エンドポイントはPUT
とPATCH
の両方を許容するため、同じパスのエンドポイントが2つになります。
そのため上記のサンプルではController, actionのペアで重複を除外しています。
認可プラグを全てのControllerで有効にします。
全てのControllerを対象にする場合、下記のようにMyApp.controller/0
に追加するのが良いでしょう。
defmodule MyApp do
def controller() do
quote do
use Phoenix.Controller, namespace: MyApp
import Plug.Conn
alias MyApp.Router.Helpers, as: Routes
plug MyApp.Plug.EnsureAuthorized # <= 追加
action_fallback MyApp.FallbackController
end
end
end
これで全てのController, actionの前にMyApp.Plug.EnsureAuthorized
が呼ばれ認可を行うことができるようになりました。
以上です。このエントリでは、Phoenixのプラグ機能を使ってエンドポイント毎の認可を実装する方法を説明しました。
コメントを送る
コメントはブログオーナーのみ閲覧できます