Route a request based on `conn.assigns` in Phoenix

I need to show the user different pages based on if they are logged in or not. In Rails, I would have done this by routing / to a different controller#action based on whether the user is logged in. However, I looked through the Phoenix code and documentation and there doesn’t seem to be a way to route a user based on the values of conn.assigns. The current work around I have is:

def index(conn, _params) do
  if user_signed_in?(conn) do
    render(conn, "index.html")
  else
    redirect(conn, to: "/dash")
  end
end

I know that I could do pattern matching in the index and render different templates, but I wanted these two to be in different controllers for maintainability.

When navigating to / just show the index, regardless of I’m logged in or not! Sometimes I wan’t to see it on purpose and I really hate sites which force me to log out or use an incognito tab just to check some information that’s presented there.

Content shouldn’t change wether I am logged in or not. I can see that there is the necessity to make it accessible based on this fact, but please do not hide any public information from your logged in users just because you like it to have their dash-board on /!


To answer your actual question, it should be possible to do what you want using a Plug which checks if a user is authenticated and puts some info in the conn. Based on this info you can then switch the pipeline used for routing.

I can’t give you an example of this, because I never had implemented such a thing.

1 Like

You have a valid point about hiding public info. However, one version of this page is a landing page which just lists the features and has a call to action. I want the user to see a dashboard once they are logged in.

Do you have any links to the relevant documentation of how you can switch the pipeline. The only thing I found which allowed matching based on the request is the “subdomain” feature discussion at https://github.com/phoenixframework/phoenix/issues/489

Sorry, I took a closer look and I had the pipeline feature wrong in my head. I thought that the pipeline used will choose the scope, but in fact it is the other way round, the scope chooses the pipeline to pipe through.

Sorry.

2 Likes

You should look at Coherence or Phauxth that provide the helper function you need to validate if the user is logged in or not.
If the differences are not that big in the landing page you can also define just one template and there show one piece of HTML or other using:

`<%= if function_that_validates_user_login do %>
    Some HTML code
<% else %>
   Other HTML code
<%end%>`

Thanks. I already have the current_user in the conn.assigns. I am trying to see if there is a way to route differently based on this value :slight_smile:

Controllers are just plugs. I think you could create a function plug that checks user_signed_in? and calls either DashboardController.call(conn, params) or LandingController.call(conn, params). Then you can put that plug in your router e.g. get "/", :dashboard_or_landing.

1 Like

I believe this is a perfect use case for plugs.

plug :redirect_to_dashboard_if_signed_in, [] when action in [:index]

def index(conn, _params) do
  # your controller should not know of the redirection logic
  render(conn, "index.html")
end

def redirect_to_dashboard_if_signed_in(conn, _opts) do
  # I'm using cond here, but you can use the original if
  cond do
    user_signed_in?(conn) -> redirect(conn, to: "/dash") |> halt
    _ -> conn
  end
end

In cases where you need to use this plug for other actions, you can add it to the action lists, or just remove the when action in ... guard to match it against all actions.

3 Likes

Thanks, this led me down the right path :slight_smile: This is what I have in my current PageController, it is a bit ugly but it works.

  def index(conn, _params) do
    if user_signed_in?(conn) do
      conn = update_in(conn.private,
                       &(Map.put(&1, :phoenix_controller, Danny.Web.DashController)))
                       |> put_view(Danny.Web.DashView)
      Danny.Web.DashController.index(conn, conn.params)
    else
      conn
      |> render("index.html")
    end

Found the :phoenix_controller stuff from

1 Like