How to redirect a logged in user's route to another

The question is

If a logged in user visits the / route, make them redirect to the /guess route.

I have

scope "/", PentoWeb do
    pipe_through :browser

    get "/", PageController, :home
  end

and

scope "/", PentoWeb do
    pipe_through [:browser, :require_authenticated_user]

    live_session :require_authenticated_user,
      root_layout: {PentoWeb.Layouts, :root},
      on_mount: [{PentoWeb.UserAuth, :ensure_authenticated}] do
      live "/users/settings", UserSettingsLive, :edit
      live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
      live "/guess", WrongLive
    end
  end

Because the user must be logged in before redirect “/” to “/guess”, I delete the get "/", PageController, :home line , and add a new line get "/", WrongLive in live_session , to make route “/” inside the same live_session . But it shows

(CompileError) lib/pento_web/router.ex:75: undefined function get/2 (there is no such import)
    (phoenix_live_view 0.19.3) expanding macro: Phoenix.LiveView.Router.live_session/3
    lib/pento_web/router.ex:69: PentoWeb.Router (module)
    (phoenix 1.7.6) expanding macro: Phoenix.Router.scope/3
    lib/pento_web/router.ex:66: PentoWeb.Router (module)

What is the best way to do it?

1 Like

Notice that the / route isn’t a live view. That means you can’t put it in a live_session block.

Since you’re changing a single view, the easiest way would be to change that view’s behavior in its controller (I see that you’re doing the LiveView book but this is a dead-view problem).

  • One way that Phoenix checks if the requesting user is authenticated is to check if conn.assigns[:current_user] is truthy (you can do this in the controller).

  • The method used to redirect a dead-view is called redirect. Here’s a link to the relevant docs page: Controllers — Phoenix v1.7.6

Hopefully that’s enough to get you going.

1 Like

Can you explain what is a live view’s controller? I haven’t seen it in books.

If I don’t modify this part:

scope "/", PentoWeb do
    pipe_through :browser

    get "/", PageController, :home
  end

,then it means every request to route “/” will be processed by PageController’s “home” action. So PageController is the only controller that I can modify to implement the questions’s redirection. Then I write these codes in page_controller.ex:

defmodule PentoWeb.PageController do
  use PentoWeb, :controller

  def home(conn, _params) do
    if conn.assigns[:current_user] do
      redirect(conn, to: ~p"/guess")
    else
      render(conn, :home, layout: false)
    end
  end
end

I think it’s the answer. And I find that, after redirection, the user has in the same @session_id

I think there are some related and similar concepts:

  1. conn.assigns[:current_user] in plugs/controllers, to get the current_user of a conn.

  2. plug :fetch_current_user in router.ex “pipeline :browser do”, which adds a key in a conn’s assigns called current_user if the user is logged in

  3. socket.assigns.current_user in user_auth.ex on_mount function, which performs authentication for mounting live views inside a live_session.

1 Like

Put your unauthed scope at the bottom and add a “/” route in your authenticated scope. Then if they visit / while logged in they will hit the authed scope and if they aren’t logged in they will fall through to the unauthed one.

Alternatively number 2, have fetch_user in your normal pipeline and check in your controller if current_user is set.

Yeah, that’s how I would do it for a single route.

If there were multiple routes with the same requirement (redirect user to /guess) and you didn’t want to modify each controller, you could do something like this instead:

Modify the scope in the router:

lib/pento_web/router.ex

  scope "/", PentoWeb do
   pipe_through [:browser, :redirect_authenticated_user_to_guess_live]  # this line has changed

    get "/", PageController, :home

    # example route, here for demonstration purposes only:
    # get "/some-other-route", SomeOtherRouteController, :some_route
  end

(The page won’t render until we define the redirect_authenticated_user_to_guess_live/2 function that we referenced in the previous example. Let’s do that now.)

lib/pento_web/user_auth.ex

  def redirect_authenticated_user_to_guess_live(conn, _opts) do
    if conn.assigns[:current_user] do
      redirect(conn, to: "/guess") # redirect the user
    else
      conn # continue the plug pipeline
    end
  end

The page should do the same thing as your original answer without requiring any work in the controller. The logic will apply to any route in that scope.


I’ll try and explain why this works, but I’m also learning, so I’ll probably get this wrong: The reason we can use an atom to reference the function is because:

  1. We already imported PentoWeb.UserAuth in the router, so the function is available in the module namespace. (That’s also why the :require_authenticated_user function works. It’s from the same module.)

  2. There’s a function somewhere in the router that calls functions based on their names when passed as atoms. I’m not totally sure why, but that’s how it works. On some level, the function names must be exposed as atoms when they are imported.


The router is interesting because it’s one of the first things a new Phoenix developer will see, and yet it is full of some pretty complex functionality. I have faith that there’s a very good reason for how everything is laid out, but my God was it confusing to figure out what was going on there.

It really makes me appreciate the simplicity of Django’s urls.py. But then, Phoenix seems to expose the complexity to you directly, instead of hiding it away like other frameworks, which seems to give you more flexibility in the long run. So the tradeoffs are worth it IMO.

1 Like

This worked for me, very nice and clean. I wish the book included the reference implementation, because there are apparently a few ways to do it, and likely only one or two are idiomatic to Phoenix.