Bringing Phoenix Authentication to Life

Image by Annie Ruygt

With this post we reach the end a story we’ve been telling you over the past few months. It’s the tale of how we developed a LiveView auth system that you can generate with a few keystrokes. Fly.io is a great place to run your Elixir applications! Check out how to get started!

Many of us used the phx.gen.auth generator to quickly build out authentication systems in our Phoenix apps. It spits out well-designed auth logic so we can get on quickly to the more interesting parts of our app.

But LiveView has increased in popularity, and in a LiveView app the generator’s “dead view” templates and views leave something to be desired. Wouldn’t it be great if those could be LiveView as well?

The Phoenix team has developed a solution for that! We’ve been eagerly looking forward to the release of Phoenix 1.7 so we could announce some new and exciting functionality.

We’ve created an auth system that uses only LiveView, and phx.gen.auth is ready to generate it for you. The command now takes a couple of options to let you choose whether to generate your auth system using Phoenix views —as before— or using LiveView:

mix phx.gen.auth Accounts User users --live
mix phx.gen.auth Accounts User users --no-live

A whole authentication system for LiveView, with a single command? Magic! But behind the magic, we can find some interesting implementation details in the generated code.

The team took existing logic and adapted it to work with LiveView. Initially, each of the pages —Log in, Registration, Forgot Password, etc— was separated into different LiveViews. The functionality of the controllers was migrated to each of the respective LiveViews and the existing HTML was rendered using the render/1 callback. However, the process was not linear, there were a few issues to resolve before everything was working smoothly.

The main issue was to write the logged-in user data in session when submitting a form within a LiveView.

When we store data in cookies —such as the current user’s identifier—, the following process is carried out through HTTP requests:

The browser identifies the set-cookie header and stores the cookie returned in the HTTP request response. We can’t do this directly from a LiveView, because LiveView-server communication goes through a websocket connection —no HTTP requests—. So how can we store session data?

The solution we settled on: go ahead and make an HTTP route call to a controller when the user submits their log-in or sign-up form —but validate the form data within the LiveView first, to make sure we don’t pop out of our LiveView unnecessarily.

You can find more details on how we did this, with the :action attribute of Phoenix forms together with the :phx-trigger-action LiveView form attribute, in “Triggering a Phoenix controller action from a form in a LiveView”.

You can find some examples in login_live.ex and _registration_live.ex files.

Faster navigation

We also took care to make navigation between related LiveViews (like the sign_in and forgot_password views) even more efficient and introduced the concept of LiveSessions. By grouping live routes into a LiveSession, we can navigate between them using the existing websocket connection, thus avoiding making extra HTTP requests. Let’s see it in action!

That’s really cool but it was only half the job. We also used live_redirect/2 from the Phoenix.LiveView.Helpers module to redirect between LiveViews in the same session —you won’t find live_redirect in your generated code, though; more on that in a minute—.

This is not all that LiveSessions can do, you can check how we used them together with Hooks to define different authorization strategies, and much more in “LiveSessions in action”.

Take a look at the code in router.ex

Testing the auth system

Once we had a working version, we also wanted to test that everything worked as expected. We used some functions of the LiveViewTest module to test, for example, that the LiveViews displayed the expected live errors and gave the user the necessary information, or test that the navigation between LiveViews was successful.

You can check more examples in “Testing LiveView forms”.

You can find several examples in the test/your_app_web/live/ directory.

The new LiveView and Phoenix features

But this is not all you will find when using the phx.gen.auth generator, you will also find examples of new features introduced in LiveView 0.18!

All LiveViews in the auth system use the UI components from the CoreComponents module (generated by creating a new project with mix phx.new). These components were designed by the Tailwind team exclusively for Phoenix and are defined using the new :attr and :slots macros, taking a look at them is an excellent way to understand these new function component options.

Remember the live_redirect function we used for faster navigation? Well, we won’t actually find it in our auth system, since it was deprecated. Instead, we’ll find the new link/1 component of the Phoenix.Component module, where the :navigate option was used as a substitute.

You will also find examples of another of the Phoenix 1.7 features that the community is most excited about: verified routes!

Fly.io ❤️ Elixir

Fly.io is a great way to run your Phoenix LiveView app close to your users. It’s really easy to get started. You can be running in minutes.

Deploy a Phoenix app today!

Tell us, have you already used the new generator? What was your favorite functionality?