annad

annad

LiveView and Phx.Gen.Auth: How to have an unauthenticated liveview pass to an authenticated one

I’m posting this for developers who are totally new to Phoenix like myself. This is all probably obvious to more skilled Phoenix developers, but I spent a lot of time figuring this out so thought I might save others time by posting the solution.

SOLUTION SUMMARY: This explains how to have an unauthenticated liveview hand off to a set of authenticated liveviews. Authorization is being handled by phx.gen.auth.

APP GOAL: I want to present the user with all of the available groups. If they like a group, I want them to “Save” it to their own repository of saved groups called My Groups. This means that the “Index” and “Show” pages will be unauthenticated and allow anyone to view them. But if the user clicks “Save”, then they need to be authenticated and passed to the apply_action “save” functionality.

WHAT DID NOT WORK: I had autogenerated liveviews for “groups” using phx.gen.live. I also had installed phx.gen.auth and had authentication completely working. To achieve my goal, I thought that the only thing I needed to do was change the router like this:

scope "/", MyApp do
    pipe_through :browser

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

end

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

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

end

Side note: If I ran all of the “group” liveviews as UNauthenticated, then it worked fine. And if I ran all of the “group” liveviews as Authenticated, then it ran fine. It was when I tried to split the functionality that things got tricky.

First, I struggled with how to make the “mount” function in Index only request the authenticated user IF the user had asked to save the “group.” I posted on Elixir Forum and @kokolegorille pointed me in the right direction. He explained that it was better to keep the unauthenticated functionality in one set of liveviews and the authenticated functionality in another set.

Once I separated the functionality, my router looked like this:

scope "/", MyApp do
    pipe_through :browser

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

end

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

    live "/mygroups", GroupLive.MyGroups, :index
    live “/mygroups/:id/edit", GroupLive.MyGroups, :edit
    live “/mygroups/:id/save", GroupLive.MyGroups, :save
    live “/mygroups/new", GroupLive.MyGroups, :new
    live “/mygroups/:id/show/edit", GroupLive.Show, :edit

end

I added two new files to my group_live directory:

my_groups.ex
my_groups.html.leex

** See note at bottom about big naming convention mistake I made that cost me hours.

THE HAND OFF

The code inside GroupLive.Index remained relatively the same, although I deleted all of the functionality for everything except :index because it was no longer needed.

The tricky part came with “index.html.leex.” First I removed the modal code that was autogenerated. It is for “new” and “edit.” That functionality would not happen in an unauthenticated page, so I deleted that from the top of the html.leex. Next, I needed to add the functionality for a “Save” link.

I needed to call a Router helper to redirect to the MyGroups liveview, but I could not figure out how to do that. I read in hexdocs that “Phoenix automatically generates a module Helpers inside your router which contains named helpers to help developers generate and keep their routes up to date.” I looked EVERYWHERE for that module and could not find it, so I couldn’t figure out where that functionality was coming from. Again, @kokolegorille helped out and pointed me to the tool for figuring out the routes. You want to run the following command: mix phx.routes. When I did that, I found all of the routes for my app:

group_index_path  GET     /groups                                    Phoenix.LiveView.Plug :index
group_show_path  GET     /groups/:id                               Phoenix.LiveView.Plug :show
group_my_groups_path  GET     /mygroups                      Phoenix.LiveView.Plug :index
group_my_groups_path  GET     /mygroups/:id/edit          Phoenix.LiveView.Plug :edit
group_my_groups_path  GET     /mygroups/:id/save        Phoenix.LiveView.Plug :save
group_my_groups_path  GET     /mygroups/new              Phoenix.LiveView.Plug :new
group_show_path  GET     /mygroups/:id/show/edit          Phoenix.LiveView.Plug :edit

Here are links to useful documentation:
Router: Phoenix.Router — Phoenix v1.8.8
Routing: Routing — Phoenix v1.8.8
LiveView.Helpers: https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html

Now that I had the route, I could redirect to my authenticated pages. I changed the code in index.html.leex to this:

<td>
          <span><%= live_redirect "Show", to: Routes.group_show_path(@socket, :show, group) %></span>
          <span><%= live_redirect "Save", to: Routes.group_my_groups_path(@socket, :save, group) %></span>
</td>

NOTE: you need to use live_redirect NOT live_patch because you are passing functionality to a new liveview.

Inside GroupLive.MyGroups, I added the authentication functionality to mount so that it grabbed the current_user. A lot of the other code was just copied over from Index.ex (the new, edit, delete) functionality.

NAMING CONVENTION MISHAP

Sadly, things still didn’t work. I ran into one more problem that revolved around naming conventions. I named this new liveview “MyGroups” and the file names were originally “mygroups.ex” and “mygroups.html.leex.” NOTE the camel case in the liveview name. I didn’t think anything of it, but it wreaked some havoc. What I didn’t realize is that the liveview name (with a camel case) required that I name the files my_groups.ex and my_groups.html.leex (note the underbar between my_groups). I ran into a series of errors as a result of my files being originally named mygroups.ex and mygroups.html.leex. I finally fixed the file names, BUT I forgot to fix my routes in the Index file. I kept getting the following error:

function MyApp.Router.Helpers.group_my_groups_path/3 is undefined or private
Called with 3 arguments 1. Socket ..., 2. :edit and 3. %Group{...}

I went crazy trying to figure out why it thought the function was undefined. Many thanks to @axelson who caught the typo in my code. My original index.html.leex had the following router path: Routes.group_mygroups_path(@socket, :save, group) BUT it needed to be
Routes.group_my_groups_path(@socket, :save, group). It was the underbar between my_groups that cost me hours of reading documentation and trying to figure out the problem.

Moral of the story: Be really really careful about the naming conventions.

First Post!

kokolegorille

kokolegorille

You might use better tooling to avoid spending hours on such typo. Elixir LS catch those kind of mistakes.

Your next step is metaprogramming, it makes the code harder, but remove tons of duplicate code. It’s also a good way to understand how Phoenix work.

Last Post!

annad

annad

Thank you for that Elixir LS tip! I’m going to definitely plug that into Visual Studio.

I had a good laugh over the metaprogramming. I am sooooooooooo not ready for that. I’d be happy if I could just get my app to work! ha ha ha But I’ll keep that in mind if I ever get to the point where I’m not chasing down typos, bad routes, loop errors and about a million compilation errors. :smiley:

Where Next?

Popular in Guides/Tuts Top

dgamidov
Hi folks, Just a short instruction. Maybe it will help somebody. Install boostrap with deps and Sass: cd assets npm install jquery ...
New
redfloyd
Greetings fellow alchemists ! I have started to write an open-source interpreter in Elixir (GitHub - nicolasdilley/dwarf-interpreter: Th...
New
niku
I write an article Parameterized testing with ExUnit.The key concept is using ExUnit.Case.register_test/4 such as ExUnit.start() defmod...
New
hauleth
Some time ago someone suggested me to write article about how I have configured my Vim to work with Elixir, now there it is: https://med...
New
egze
I was preparing to deploy a production application to AWS Fargate, and to practice I wanted to play with DNS polling and node discovery o...
New
magnetic
Hey :waving_hand:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and ...
New
TwistingTwists
This is a thread to note down things/best practices encountered in LiveBeats App as I explore the source code. https://github.com/fly-...
New

Other popular topics Top

minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31494 112
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement