david234

david234

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

Showing Posts 8 to 1

NduatiK

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,

  1. The podcast for that id is loaded
  2. 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.
  3. The policy return a bool that gives access if the user is an admin or a contributor to the post
  4. If 3 returns true, call the update method. Else reject the request
mathieuprog

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.

david234

david234 OP

Thank you very much for your time and very neat explanation.

One small dout

My current router is like this

scope “/admin”, LogisticsWeb.Admin, as: :admin do

pipe_through [:browser, :admin, LogisticsWeb.Plugs.AuthenticateAdmin]

get "/", DashboardController, :index

resources "/admins", AdminController

get "/locations", LocationsController, :show
get "/locations/add", LocationsController, :add
post "/locations/add", LocationsController, :create

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

end

I am changing like this. Is this a proper way to achieve this?

	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

  scope "/admin", LogisticsWeb.Admin, as: :admin do

    pipe_through [:browser, :admin, LogisticsWeb.Plugs.AuthenticateAdmin]

    get "/", DashboardController, :index

    resources "/admins", AdminController

  end

  scope "/admin/location", LogisticsWeb.Admin, as: :admin do

    pipe_through [:browser, :admin, LogisticsWeb.Plugs.AuthenticateAdmin, :ensure_location_routes_authorized]

    get "/locations", LocationsController, :show
    get "/locations/add", LocationsController, :add
    post "/locations/add", LocationsController, :create

  end


  scope "/admin/barcodes", LogisticsWeb.Admin, as: :admin do

    pipe_through [:browser, :admin, LogisticsWeb.Plugs.AuthenticateAdmin, :ensure_barcode_routes_authorized]

    get "/barcodes", ProductsController, :show_barcodes
    get "/barcode/add", ProductsController, :add_barcode
    post "/barcode/add", ProductsController, :create_barcode

  end
mathieuprog

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.

kokolegorille

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
david234

david234 OP

I checked your code but I could able to figure out how I can use it to my situation.

For example, A user with this permission “can_admin_barcode” Can be allowed to view only below routes.
This value “can_admin_barcode” will be stored in session.

get "/barcodes", ProductsController, :show_barcodes
get "/barcode/add", ProductsController, :add_barcode
post "/barcode/add", ProductsController, :create_barcode

Can you give me insight on how to achieve this using your library.

david234

david234 OP

Thanks. I will check now.:smiley:

mathieuprog

mathieuprog

I created a similar Plug which protects routes against authorization rules. It’s on GitHub and the source is trivial:
https://github.com/mathieuprog/auth_z/blob/master/README.md#protecting-routes-against-unauthorized-users

Let me know if it doesn’t answer your case.

— All posts loaded —

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New
dillonoconnor
Is there any way to avoid the Hologram compiler running when using iex? It seems like the front-end code could potentially be disregarded...
New
thiagogsr
** (ArgumentError) expected :max_attempts to be a positive integer, got: {:@, [line: 10, column: 19], [{:max_attempts, [line: 10, column:...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews