Two root pages; one for unauthenticated users, one for authenticated users

495 Views Asked by At

I was wondering how I could implement having two pages in the same root "/" using Phoenix? One for unauthenticated users, and one for authenticated users. Examples of use cases where this happens are LinkedIn and Facebook, where a login-page is served on "/" and an application on "/" after logging in.

I use Guardian for authentication, and have my router set up as:

 pipeline :auth do
   plug Guardian.Plug.EnsureAuthenticated, handler: App.AuthHandler
  end
 pipeline :unauth do
   plug Guardian.Plug.EnsureNotAuthenticated, handler: App.AuthHandler
 end

 scope "/", App.Web do
   pipe_through [:browser, :unauth]

   get "/", FrontController, :index
 end
 scope "/", App.Web do
   pipe_through [:browser, :auth]

   get "/page", ApplicationController, :index
 end

Where FrontController serves the pages accessible by unauthenticated users (e.g. a login-page), and ApplicationController serves the actual application.

When ApplicationController serves "/" instead of "/page", a "this clause cannot match because a previous clause at line 1 always matches" error is thrown.

I imagine using if-statements and one controller for serving both pages, unfortunately I couldn't find documentation on how to implement such a strategy.

2

There are 2 best solutions below

0
On BEST ANSWER

This isn't what you asked, but in my humble opinion a better way to handle this is to use two different namespaces, "/" for unauthenticated users and "/app" (or something) for authenticated users. This makes it easier for you and your users to distinguish between the two, and reduces system complexity. Then if an unauthenticated users tries to go to an "/app" route you can return a 401 Unauthorized response (from inside your App.AuthHandler along with a proper error message, such as "You need to be logged in to do that."

BUT if you insist on doing what you wrote, then I would just handle that scenario simply in ApplicationController.index - no need for a FrontController. That index action might look something like this:

defmodule MyApp.Web.ApplicationController do

  def index(conn, _) do
    case logged_in?(conn) do
      true ->
        user = Guardian.Plug.current_resource(conn)
        render conn, "logged_in_page.html", user: user
      false ->
        render conn, "front_page.html"
    end
  end

  # Note: This could be in a helper module
  defp logged_in?(conn) do
    Guardian.Plug.authenticated?(conn)
  end
end

This way, the user is served a different page when they go to "/" depending on whether they have a valid JWT.

Again though this approach makes your system more complicated. And generally speaking, you should avoid complexity unless you have a really good reason. Follow conventions whenever possible and you'll be grateful later. :)

0
On

Most likely, what you want to do is handle this at the controller & view render layer, not in the router.

Inside your "root" or home controller (whatever one serves up "/" as the handler) you can check in your action if you have a valid auth'd user or not, and render the view you want based on that.