0
votes

I am using phx_gen_auth for user sign in, signup, authentication in my Elixir Phoenix application. It comes with a :require_authenticated_user plug to decide if a specific route requires the user to be authenticated before accessing.

If I have a resource of posts in my router.ex that gives me all the standard actions:

resources "/posts", PostController

How can I best organize certain routes to require the user to be signed in? For example, viewing all of the posts or viewing a single post shouldn't require login. But creating, editing, updating, deleting a post should require the user to be signed in.

Is there a way to manage which routes require auth without specifying all of my routes separately in two separate scope blocks that have separate pipe_throughs?

2

2 Answers

1
votes

As it is stated in the documentation for Phoenix.Router.pipeline/2

Every time pipe_through/1 is called, the new pipelines are appended to the ones previously given.

That said, this would work:

scope "/", MyWeb do
  pipe_through [:browser]
  get "/index", PostController

  pipe_through, :auth
  get "/show", PostController
  post "/create", PostController
  put "/update", PostController
end

Another solution would be to use the plug directly in the controller

defmodule MyWeb.PostController do
  plug :auth when action in ~w|show create update|a

  def show(conn, params) do
    # ...
  end
end
1
votes

The only way I know to do this is to use 2 scopes. Here is an example where I declare the same scope twice, but one of them uses an extra :auth pipeline. Notice how I use the :only option to specify which controller methods are allowed:

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :auth do
    plug MyAppWeb.Auth
  end

  scope "/", MyAppWeb do
    pipe_through :browser

    resources "/posts", PostController, only: [:index]
  end

  scope "/", MyAppWeb do
    pipe_through :browser
    pipe_through :auth

    resources "/posts", PostController, only: [:show, :new, :create]
  end

The trick is to use :only to say which of the functions in your controller are allowed to be called in this way. The router will match the first one it finds, so if you use the same method twice in both scopes, say :index, then it will use the first scope (that is, the first lexically defined scope. -the first one in the file).

I hope this makes sense. I can explain more if you want.

P.S. Maybe experiment with this and see if it works:

  scope "/", MyAppWeb do
    pipe_through :browser
    resources "/posts", PostController, only: [:index]

    pipe_through :auth
    resources "/posts", PostController, only: [:show, :new, :create]
  end