david234
Phoenix user role based access control
I have the following routes
get "/locations", LocationsController, :show
get "/locations/add", LocationsController, :add
post "/locations/add", LocationsController, :create
get "/units", LocationsController, :show_units
get "/unit/add", LocationsController, :add_units
post "/unit/add", LocationsController, :create_units
get "/products", ProductsController, :show
get "/product/add", ProductsController, :add
post "/products/add", ProductsController, :create_product
get "/barcodes", ProductsController, :show_barcodes
get "/barcode/add", ProductsController, :add_barcode
post "/barcode/add", ProductsController, :create_barcode
I have three permission groups(admin can add more permission group)
can_admin_location
can_admin_products
can_admin_barcode
For example, You can see the below image to understand more what i am trying to achieve
I want to restrict route access based on permission given
For example if the below permission
can_admin_barcode (can use only below routes)
get "/barcodes", ProductsController, :show_barcodes
get "/barcode/add", ProductsController, :add_barcode
post "/barcode/add", ProductsController, :create_barcode
Then admin can change the user access level for this permission group
can_admin_barcode (can only view barcodes and only the below route will be accessible by this user)
get "/barcodes", ProductsController, :show_barcodes
Routes blocking must happen dynamically
I am storing current logged in user permission in session. I believe i can achieve this Route restriction using plugs.
Can someone share some insight on how to achieve this or any example. Your help is greatly appreciated
Marked As Solved
mathieuprog
Assuming that the user session is stored in conn.assigns under the :current_user key (if not, just adapt) :
defmodule MyAppWeb.AuthorizationPlug do
def init(opts), do: opts
def call(conn, opts) do
current_user = Map.get(conn.assigns, :current_user)
resource = Keyword.fetch!(opts, :resource)
authorize(conn, current_user, resource)
end
# below you will list all your authorization rules:
def authorize(conn, %{permissions: permissions}, :barcode_routes) do
# check that the :can_admin_barcode permission is included in the permissions
# if not, set status to 403 and halt the conn
conn
end
def authorize(conn, %{permissions: permissions}, :location_routes) do
# ...
end
end
pipeline :ensure_barcode_routes_authorized do
plug MyAppWeb.AuthorizationPlug, resource: :barcode_routes
end
pipeline :ensure_location_routes_authorized do
plug MyAppWeb.AuthorizationPlug, resource: :location_routes
end
# etc
scope "/barcode", MyAppWeb do
pipe_through [:browser, :ensure_barcode_routes_authorized]
get "/", ProductsController, :show_barcodes
get "/add", ProductsController, :add_barcode
post "/add", ProductsController, :create_barcode
end
It’s just one way to achieve authorization over routes.
Also Liked
NduatiK
Continuing the discussion from Phoenix user role based access control:
You can also check out changelog.com’s approach. They define an authorize plug that allows them to define a policy for each route on a controller.
# authorize.ex
defmodule ChangelogWeb.Plug.Authorize do
import Plug.Conn
import Phoenix.Controller
def init(opts), do: opts
def call(conn, policy_module) when not is_list(policy_module),
do: call(conn, [policy_module, nil])
def call(conn, [policy_module, resource_name]) do
user = conn.assigns.current_user
resource = conn.assigns[resource_name]
if apply_policy(policy_module, action_name(conn), user, resource) do
conn
else
conn
|> put_flash(:result, "failure")
|> redirect(to: ChangelogWeb.Plug.Conn.referer_or_root_path(conn))
|> halt()
end
end
defp apply_policy(module, action, user, nil), do: apply(module, action, [user])
defp apply_policy(module, action, user, resource), do: apply(module, action, [user, resource])
end
For example in the admin postcontroller these two plugs are defined.
plug :assign_post when action in [:edit, :update, :delete, :publish, :unpublish]
plug Authorize, [Policies.Admin.Post, :post]
For actions where an existing post is being manipulated, that post is first preloaded.
Then, permissions are checked on a per action basis using the Policies.Admin.Post policy which looks like:
defmodule Changelog.Policies.Admin.Post do
use Changelog.Policies.Default
def create(actor), do: is_admin_or_editor(actor)
def index(actor), do: is_admin_or_editor(actor)
def show(actor, post), do: is_admin_or_post_contributor(actor, post)
def update(actor, post), do: is_admin_or_post_contributor(actor, post)
def delete(actor, post = %{published: false}), do: is_admin_or_post_contributor(actor, post)
def delete(_actor, _post), do: false
def publish(actor, post), do: is_admin_or_post_contributor(actor, post)
def unpublish(actor, post), do: publish(actor, post)
# ...
end
Taken all together, when a user tries to update a post,
- The podcast for that id is loaded
- The podcast and the user are loaded in the Authorize plug. Because the resource exists ie the podcast assign is there, the update method of the post policy is called with the user and the podcast. For create, the podcast assign would be nil and the create function would be called with just the user.
- The policy return a bool that gives access if the user is an admin or a contributor to the post
- If 3 returns true, call the update method. Else reject the request
kokolegorille
The plug does not know about the routes… but a simple plug like this might do
def MyPlug do
import Plug.Conn
def init(opts \\ []), do: opts
def call(conn, _opts) do
if can_admin_barcode && String.start_with?(conn.request_path, "/barcodes") do
conn
else
conn
|> halt()
end
...
end
end
mathieuprog
Yeah, that’s how I do it. You can see that I in fact attach an atom to each set of routes. Like all these barcode routes and the :barcode_routes atom.
Then in the Plug I retrieve that atom (representing thus that set of routes), and I check if the user has the right permissions for those routes.
Maybe there’s an easier way for you, but I just show you how I implemented it. This way, if you want to check something more than the permissions property in the future, there are no restrictions and you can check other user attributes.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #javascript
- #code-sync
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance










