Pass a specific route in a scope through a plug

In the following code:

  scope "/", FooWeb do
    pipe_through :browser

    get "/", PageController, :index
    get "/about", PageController, :about
    
    ...

    get "/login/web_auth", WebAuthController, :show

    ...

I have a route get "/login/web_auth", WebAuthController, :show, which needs to be additionally piped through the plug :requires_login on top of the :browser pipeline.

I have tried to include the plug in the show function (the other routes in the controller don’t the need to be piped through :requires_login. So, I didn’t include it in the module definition), which failed.

I have considered:

  1. Making a nested scope login/web_auth/ which is piped through :browser and :requires_login
    with a get "/" route.
  2. Making a new controller for the route which needs :requires_login.

However, I haven’t tried the above as I wasn’t sure which is more ideal.

What is the common convention is such a scenario? How would you solve the problem?

I personally try to keep my routing flat. The part that was not obvious to me was that you can have multiple scopes on the same level with the same prefix:

# 
# Web app routes
# 
scope "/", MyAppWeb do
  pipe_through :browser

  #
  # Authentication.
  #
  scope "/" do
    pipe_through :require_unauthenticated_user

    get "/sign-in", SessionController, :new
    post "/sign-in", SessionController, :create
  end

  #
  # Authenticated users only.
  #
  scope "/" do
    pipe_through :require_authenticated_user

    get "/", PageController, :index
    # ...
  end
end

So basically scope is a way to group routes (but it can introduce a route prefix too, like "/api").

1 Like

I see. I didn’t occur to me too that you can do something like that. I will try and see if it works :slight_smile:

Looking at the Hexpm code, I found that in the Controller definition, the variable action is in scope.
action has the value of your action (eg: :index, :show, etc.). We can compare action and apply a plug selectively in our module declaration. That would be something like this:

defmodule FooWeb.WebAuthController do
  use FooWeb, :controller

  plug :requires_login when action == :show

This in my opinion is even better than @stefanchrobot’s solution (which is pretty good!).

Yes, you can conditionally plug in the controller. This makes controllers “self-contained”. On the other hand, if you plug something all the time, doing this in the router removes a lot of duplication.

The great thing about Phoenix is that you can do it the way you see fit. You could even do something like:

use FooWeb, :controller_that_requires_auth

There’s one big advantage about putting plugs in the controller. It’s that they know the path/controller/action they are associated with, while plugs in routers run before information about the matched controller are set on the conn. This can be quite useful for certain types of plugs.

2 Likes