Phx.gen.auth mixing authenticated and unauthenticated pages

I am trying to use phx.gen.auth with some but not ALL of my pages and I’m running into errors because I have to assign current user in mount … even on the page that doesn’t require authentication.

Here are the links that will straddle both authentication and non-authentication BUT they all share the same “mount”

# The first general link is under pipe_through :browser because it won't require authentication.  I want anyone to be able to see the list of "groups."

live "/groups", GroupLive.Index, :index

# These links are under 
    pipe_through [:browser, :require_authenticated_user]
because these all need authentication.

live "/groups/new", GroupLive.Index, :new: 
live "/groups/:id/edit", GroupLive.Index, :edit
live "/groups/:id", GroupLive.Show, :show
live "/groups/:id/show/edit", GroupLive.Show, :edit

I added the following in mount:

def mount(_params, session, socket) do
    socket =
      socket
      |> assign(:groups, list_groups())
      |> assign_current_user(session)

    {:ok, socket}
  end

Assign_current_user(socket, session) is defined in LiveHelpers:

def assign_current_user(socket, session) do
    assign_new(
      socket,
      :current_user,
      fn -> Users.get_user_by_session_token(session["user_token"])
    end )
  end

When I run http://localhost:4000/groups (the page that doesn’t require authentication), I get this error:

** (ArgumentError) nil given for `token`. comparison with nil is forbidden as it is unsafe. Instead write a query with is_nil/1, for example: is_nil(s.token)

So then I added this to mount:

if not is_nil(session["user_token"]) do
      socket = assign_current_user(socket, session)
    end

That made the “/groups” page available without logging in BUT I can still access the form_component and delete pages without logging in as well! ARG!!!

How do I straddle the two worlds where some pages force authentication and others don’t when they are ALL sharing the same “mount” function?

Hi

Try having 2 groups of pipe throughs in your router like

  pipeline :browser do
    …
  end

  pipeline :auth do
    …
  end

  pipeline :authed do
    …
  end

  scope "/public/", MyApp.Public do
    pipe_through [:browser, :auth]
  end

  scope "/", MyApp do
    pipe_through [:browser, :auth, :authed]
  end

Put the public scope in first so it gets hit first

It is not what You think it is… It is not for authenticated routes.
It means… redirect if the user is already authenticated :slight_smile:
You might use it to forbid authenticated users to access certain pages, like the login page.

Only unauthenticated users can access those routes. So, no user token.

You should use this one.

pipe_through [:browser, :require_authenticated_user]

You are sure to have a user token in the session in this pipeline.

Sorry. I copied the wrong line above! I do indeed have those routes under:

    pipe_through [:browser, :require_authenticated_user]

So that doesn’t appear to be the issue.

I’m super confused. Is this a routing issue? I thought it was an issue with mount.

You should not have user token issue with this pipeline. It should be present.

So, would I not use these pipes?

    pipe_through :browser
       ** this one for non auth

    pipe_through [:browser, :require_authenticated_sketcher]
      ** this one for auth

That’s the weird thing. I do have it set up just like all of the examples. But I’m getting incorrect behavior. I am able to see “/groups” without being logged in. Which is great. But when I click on “New group” or “Edit” … it opens up the modal form. It doesn’t ask for a password. Despite the fact that all of those routes “require_authenticated_sketcher.” That’s what made me think that I must be doing something wrong with the mount function.

Now I understand your problem better. You are using the same liveview for both public and private access. It’s probably easier to use separate.

Like GroupLive.PublicIndex, :index

1 Like

So would I need to create two separate files? One that is GroupLive.PublicIndex and one that is GroupLive.Index? So they would be essentially identical. But one would be used when the user is authed and the other when the user is not authed. Is that right?

Yes, it is not an obligation to share the same liveview for index, new, update.

This is convenient for modal forms, but You can have GroupLive.Index / New /update in separate files.

But that will mean that I’ll have identical functionality in two places. So I’d have to duplicate both index.ex and index.html.leex. Any time I make changes to index functionality, I’d have to do it in two places. Is that really the only way?

No, just separate Index, which is public, and the rest. No need to duplicate.

Sorry. I’m really really new at Phoenix so I don’t have a strong understanding of how things work. But if I understand you correctly, what you’re saying is to just pull Index and Index.html.leex OUT of the current live view and put it in it’s own liveview called public_live. Then have the web pages point to that when the user goes back to the “/groups” webpage. And since it will be in a liveview that is public, it won’t matter whether the user is authenticated or not. Does that sound right?

What I would do in your case is

No need to rename index, but remove new and edit code.

Add a new liveview… call it new_or_edit, and add the functionalities You have removed from index.

and change those routes to…

live "/groups/new", GroupLive.NewOrEdit, :new: 
live "/groups/:id/edit", GroupLive.NewOrEdit, :edit

You could do it in only one, but that would add a lot of conditionals, to check if the user is authenticated.

How will that effect the routing code at the bottom of the index templates:

<span><%= live_patch "New Group", to: Routes.group_index_path(@socket, :new) %></span>

<span><%= live_patch "Edit", to: Routes.group_index_path(@socket, :edit, group) %></span>

In fact, You might have to change to live_redirect instead of live_patch.

But even if I use live_redirect, won’t Routes.group_index_path try to redirect within the group liveview? It won’t know to redirect to NewOrEdit.

It’s the path that defines the link helper, not the underlying liveview module.

I have unfortunately never understood how the live_patch and live_redirect functions work. Routes is an alias for MyApp.Router.Helpers … but I can’t find “Helpers.” I just have router.ex. I don’t have a module called “Helpers” in Router. So I haven’t figured out how that path gets sent. I know that there is a function like Routes.group_index_path(@socket, :new) that redirects to a new path … but I’m lost on how it does it.

Can you tell me how to redirect between group_live and new_or_edit live?

live patch is used to route in the same liveview process, live redirect is when You jump to a new liveview.

This is how You tell which module to use for a given route.