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 1 to 8

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.

david234

david234 OP

Thanks. I will check now.:smiley:

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.

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
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.

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

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.

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
— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
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
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
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
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
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
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews