Access matched Route from phoenix Conn

Given a Plug.Conn that went through a Router like this:

get("/foo/:bar", FooController, :get_foo)

For a request like GET /foo/42, I can use conn.request_path to acess the String /foo/42, but is there a way to get the /foo/:bar string that was matched in the Router ?

1 Like

No public API exists for that today. What would you like to do with the info?

This is not pretty and perhaps not even an appropriate use for the module, however, it will return what you are looking for:

route = conn.request_path
        |> Phoenix.Router.Resource.build(YourWebApp.FooController, [])
route.member[:path]

I’m trying to write a plug that does authentication based on which route was matched.

In my mind, the plug would load a CSV table to know which role is needed to access a given route.(I tried using macro inside the router, but it lacks the lisibility of a CSV table.)

method,route,role_1,role_2,....
Get, /foo, yes,yes,...
Get,/too/:foo_id,no,yes,...
...

Then, given a Conn , knowing which route and method was matched would give me enough info to check the right row in my table.

I was able to half bake something by using a regex in the CSV file, but ideally, accessing the route would make my life easier :slight_smile:

1 Like

I gave you a solution above :grinning:

Sorry, i havn’t been able to test it yet, I’ll give it a shot and tell you if it worked !

If you have not already explored this, you can write a plug and break up the routes in pipelines within your routes module. Such a plug could set a role to be used by your authentication module within a controller, or even within the route itself – depending on how you have your authentication system setup:

pipeline :auth_user do
  plug MyWebApp.Plug.Auth, :user
end

pipeline :auth_employee do
  plug MyWeb.Plug.Auth, :employee
end

scope "/", MyWeb do
  pipe_through [:browser, :auth_user]

  get "/login/user", AuthenticationController, :new
end

scope "/", MyWeb do
  pipe_through [:browser, :auth_employee]

  get "/login/employee", AuthenticationController, :new
end

So perhaps in your auth plug you would assign the role set within the defined pipelines (:user, :employee).

You could also do this in the controller at the new method level as well. The options are quite limitless :slight_smile:

The problem is in my case, I have a “matrix” of roles and endpoint, and there is no “simple” mapping between endpoints and which roles have access to them.

If I followed your example, I would have an explosion of different plug configuration (potentially, a different configuration for each endpoint !!)

pipeline role_1 do 
  # Endpoints availble only to users with role "role_1"
  plug MyApp.Plug.Auth, roles: ["role_1"]  
end

pipeline role_2 do
  #Endpoints available only to users with role "role_2"
  plug MyApp.Plug.auth, roles: ["role_1"]
end

....

pipeline role_1_or_role_2 do
  #Endpoints available only to users with role "role1", or, "role_2", but not "role_3"
  plug MyApp.Plug.auth, roles: ["role_1", "role_3"]
end

pipeline role_1_or_role_3 do
  #Endpoints available only to users with role "role1", or, "role_3", but not "role_2"
  plug MyApp.Plug.auth, roles: ["role_1", "role_2"]
end

...

At some point, I used a custom set of get_xxx macros that made it possible to write something like:

get_with_role("/foor/bar", FooController, :get_bar, role_in: ["role_1", "role_2"])

It worked, but it clearly lacked the visibility of a table with one row for each endpoint, and one column for each role. (Which is also much easier to reason about with our PM - arguably, our domain and permission model is a bit complex, but that’s for - good - business reason.)

I’m afraid I’m not going to use your solution, since I don’t have access to the XxxController that implemented the route inside a plug (do I ?)

Yes. you should be able to use that in a plug no problem at all. In fact you’re not calling anything from a controller, you’re just passing the controller module name, which expands to an atom.

What I mean is that i’m writing a Plug like this :

defmodule RolePlug do

  def call(conn, _) do

    # I can get the Router through which the Conn was passed, but I could not find how to get 
    # the Controller.

    route = conn.request_path
        |> Phoenix.Router.Resource.build(???????, [])
    route.member[:path]


  end 

end

Oh, I get it. So, where are you trying to get the “/foo/:bar” info at in your app? At which point, in a plug, controller?

And, yeah, I do not think the controller name is passed in the conn :neutral_face:

Hey @phtrivier so did you follow the csv method for role based access to routes as I am also implementing something in this line only. Is there a recent updated solution that you jumped onto since this thread is 2 year old.