DEV Community

Lubien
Lubien

Posted on • Edited on

The Lazy Programmer's Intro to LiveView: Chapter 2

Go to Chapter 1

Adding Authentication to Champions

Authentication and authorization are a pain in the ass. It always felt like the most boring thing ever when I started new projects. Luckily Phoenix comes with a simple yet powerful auth system built-in. In this chapter, we are going to learn how to use Phoenix Auth Generator, aka mix phx.gen.auth.

A quick tip for those who like details

We are very soon going to run a command that generates a few files and modifies existing ones. If you haven't created a git repository or committed your changes yet I strongly advise you to so you can use the diff to learn what changed.

$ git init
$ git add .
$ git commit -m Init
Enter fullscreen mode Exit fullscreen mode

What does Champions App need?

If you recall the last chapter I mentioned that this project was meant for people to record matches between themselves. That means we need to know who you are. We are going to build a way one can simply register and log in to our app.

Meet mix phx.gen.auth

As previously mentioned, mix is also an automation tool for Elixir. The Phoenix framework added a few custom commands to help generate boilerplate Phoenix code that can go very far when you need to get things done. Feel free to run mix help if you want to list all possible mix commands. For now, we care only about one: mix phx.gen.auth.

$ mix help phx.gen.auth

                                mix phx.gen.auth                                

Generates authentication logic for a resource.

    $ mix phx.gen.auth Accounts User users

The first argument is the context module followed by the schema module and its
plural name (used as the schema table name).

Additional information and security considerations are detailed in the mix
phx.gen.auth guide (mix_phx_gen_auth.html).

# A lot more info below
Enter fullscreen mode Exit fullscreen mode

Did you see what I just did? I ran mix help phx.gen.auth and got a ton of useful information. Feel free to do that whenever you feel lost before running a command. What we really care about now is the usage: mix phx.gen.auth Accounts User users. What are those 3 words that come after the command? I invite you to learn about…

Phoenix Contexts

Phoenix fundamentally uses an abstraction where you separate the bones of your code into 3 places:

  • Context modules: a place where you should put all your business logic.
  • Ecto Models: a place where you should bridge Elixir and your database.
  • Web modules: everything that handles user interactions ranging from HTML to JSON APIs.

A lot of Phoenix generator commands follow the structure: mix phx.gen.* [Context Name] [Model Name] [Model Table Name]. If we apply that knowledge into mix phx.gen.auth Accounts User users we have:

  • mix phx.gen.auth: Hey Phoenix, please generate an authentication system for me.
  • Accounts: let's call the context module Accounts, and put all my business logic there.
  • User: the Ecto model name for our users should be User, define its structure there.
  • users: and make sure the database table name is users.
mix phx.gen.auth Accounts User users
An authentication system can be created in two different ways:
- Using Phoenix.LiveView (default)
- Using Phoenix.Controller only
Do you want to create a LiveView based authentication system? [Yn] 
* creating priv/repo/migrations/20230617121436_create_users_auth_tables.exs
* creating lib/champions/accounts/user_notifier.ex
* creating lib/champions/accounts/user.ex
* creating lib/champions/accounts/user_token.ex
* creating lib/champions_web/user_auth.ex
* creating test/champions_web/user_auth_test.exs
* creating lib/champions_web/controllers/user_session_controller.ex
* creating test/champions_web/controllers/user_session_controller_test.exs
* creating lib/champions_web/live/user_registration_live.ex
* creating test/champions_web/live/user_registration_live_test.exs
* creating lib/champions_web/live/user_login_live.ex
* creating test/champions_web/live/user_login_live_test.exs
* creating lib/champions_web/live/user_reset_password_live.ex
* creating test/champions_web/live/user_reset_password_live_test.exs
* creating lib/champions_web/live/user_forgot_password_live.ex
* creating test/champions_web/live/user_forgot_password_live_test.exs
* creating lib/champions_web/live/user_settings_live.ex
* creating test/champions_web/live/user_settings_live_test.exs
* creating lib/champions_web/live/user_confirmation_live.ex
* creating test/champions_web/live/user_confirmation_live_test.exs
* creating lib/champions_web/live/user_confirmation_instructions_live.ex
* creating test/champions_web/live/user_confirmation_instructions_live_test.exs
* creating lib/champions/accounts.ex
* injecting lib/champions/accounts.ex
* creating test/champions/accounts_test.exs
* injecting test/champions/accounts_test.exs
* creating test/support/fixtures/accounts_fixtures.ex
* injecting test/support/fixtures/accounts_fixtures.ex
* injecting test/support/conn_case.ex
* injecting config/test.exs
* injecting mix.exs
* injecting lib/champions_web/router.ex
* injecting lib/champions_web/router.ex - imports
* injecting lib/champions_web/router.ex - plug
* injecting lib/champions_web/components/layouts/root.html.heex

Please re-fetch your dependencies with the following command:

    $ mix deps.get

Remember to update your repository by running migrations:

    $ mix ecto.migrate

Once you are ready, visit "/users/register"
to create your account and then access "/dev/mailbox" to
see the account confirmation email.
Enter fullscreen mode Exit fullscreen mode

Visual Studio Code with Git tab open showing 30 modified or created files

That is a huge diff. But don't be scared: let's talk about the main things you should care about immediately now.

The lib/champions directory

lib
├── champions
│   ├── accounts
│   │   ├── user.ex
│   │   ├── user_notifier.ex
│   │   └── user_token.ex
│   ├── accounts.ex
│   ├── application.ex
│   ├── mailer.ex
│   └── repo.ex
├── champions.ex
Enter fullscreen mode Exit fullscreen mode

Since our project is called champions, inside the lib folder there's a folder with that very same name that will host our business logic. When we ran mix phx.gen.auth we choose the context and model names to be, respectively, Accounts and User. We can see that here where lib/champions/accounts.ex contains the context Champions.Accounts and inside lib/champions/accounts/user.ex there's our model Champions.Accounts.User. You might have noticed but the folder and module names match.

defmodule Champions.Accounts do
  def get_user_by_email(email) when is_binary(email) do
  def get_user_by_email_and_password(email, password)
  def get_user!(id), do: Repo.get!(User, id)
  def register_user(attrs) do
  def change_user_registration(%User{} = user, attrs \\ %{}) do
  def change_user_email(user, attrs \\ %{}) do
  def apply_user_email(user, password, attrs) do
  def update_user_email(user, token) do
  def deliver_user_update_email_instructions(%User{} = user, current_email, update_email_url_fun)
  def change_user_password(user, attrs \\ %{}) do
  def update_user_password(user, password, attrs) do
  def generate_user_session_token(user) do
  def get_user_by_session_token(token) do
  def delete_user_session_token(token) do
  def deliver_user_confirmation_instructions(%User{} = user, confirmation_url_fun)
  def confirm_user(token) do
  def deliver_user_reset_password_instructions(%User{} = user, reset_password_url_fun)
  def get_user_by_reset_password_token(token) do
  def reset_user_password(user, attrs) do
end
Enter fullscreen mode Exit fullscreen mode

I've deleted a ton of code just so you can see the function names of our Accounts context purpose. When I said our business logic lives there I meant it. This context is all about doing things with user accounts. Ranging from getting data to sending recovery password emails, this will be very helpful in the future. Do note that this is just a plain Elixir module with a lot of functions, there's no magic that makes it a context, it's just a good practice on Phoenix. Later on, we will be creating more contexts and adding functions to existing ones so you can get more practice.

defmodule Champions.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :email, :string
    field :password, :string, virtual: true, redact: true
    field :hashed_password, :string, redact: true
    field :confirmed_at, :naive_datetime

    timestamps()
  end

  def registration_changeset(user, attrs, opts \\ []) do
  def email_changeset(user, attrs, opts \\ []) do
  def password_changeset(user, attrs, opts \\ []) do
  def confirm_changeset(user) do
  def valid_password?(%Champions.Accounts.User{hashed_password: hashed_password}, password)
  def validate_current_password(changeset, password) do
end
Enter fullscreen mode Exit fullscreen mode

If you go to lib/champions/accounts/user.ex you're going to see something like the code above. Once more I've deleted the function code so it's easier to see what does it have. Notice it starts with a simple mapping of what our model contains: email, password, hashed_password and confirmed_at. You can also see there's timestamps() there, this macro adds two fields: inserted_at and updated_at. There's really a ton of unpacking here on the future but for now think of Ecto models as an abstraction to map our database data to Elixir and the other way around.

For now we will ignore the other files created on lib/champions and will talk about them as we need later.

The lib/champions_web folder

lib
├── champions_web
│   ├── components
│   │   ├── core_components.ex
│   │   ├── layouts
│   │   └── layouts.ex
│   ├── controllers
│   │   ├── error_html.ex
│   │   ├── error_json.ex
│   │   ├── page_controller.ex
│   │   ├── page_html/
│   │   ├── page_html.ex
│   │   └── user_session_controller.ex
│   ├── endpoint.ex
│   ├── gettext.ex
│   ├── live
│   │   ├── user_confirmation_instructions_live.ex
│   │   ├── user_confirmation_live.ex
│   │   ├── user_forgot_password_live.ex
│   │   ├── user_login_live.ex
│   │   ├── user_registration_live.ex
│   │   ├── user_reset_password_live.ex
│   │   └── user_settings_live.ex
│   ├── router.ex
│   ├── telemetry.ex
│   └── user_auth.ex
└── champions_web.ex
Enter fullscreen mode Exit fullscreen mode

Phoenix likes to separate business logic from user interfaces, which includes both HTML and APIs. It's a good practice for you to learn how to separate your concerns between your app and app_web folders once you understand better. For now I'd like to draw your focus on on these folders:

  • components: here we put generic Phoenix Components that can be reused over our application such as custom button, form inputs, data tables etc.
  • controllers: here lies both API controllers (files ending with _json.ex) and plain HTML controllers (ending with _html.ex). Any HTML here is not a LiveView (known to some as dead views).
  • live: the name says all, our LiveViews will be stored here. Phoenix auth generator even created some for you. Each LiveView can be reused in one or more pages. We'll talk more about that later.

What we got after all?

We need to do two more things before we proceed: install the new dependencies (bcrypt_elixir) and migrate our database to create our users tables. Stop your server and run the following commands:

$ mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
# more stuff...
* Getting bcrypt_elixir (Hex package)
* Getting comeonin (Hex package)
* Getting elixir_make (Hex package)

champions on  main [!?] via 💧 v1.14.1 took 3s 
[I] ➜ mix ecto.migrate
# Code compiling
Generated champions app

10:51:19.938 [info] == Running 20230617121436 Champions.Repo.Migrations.CreateUsersAuthTables.change/0 forward

10:51:19.942 [info] execute "CREATE EXTENSION IF NOT EXISTS citext"

10:51:20.072 [info] create table users

10:51:20.116 [info] create index users_email_index

10:51:20.124 [info] create table users_tokens

10:51:20.152 [info] create index users_tokens_user_id_index

10:51:20.159 [info] create index users_tokens_context_token_index

10:51:20.176 [info] == Migrated 20230617121436 in 0.2s
Enter fullscreen mode Exit fullscreen mode

Restart your server once more with mix phx.server, you should see two links to login and registration pages on the top right. Let's register our first user. After registering you should be automatically logged in. One neat feature you should look into immediately is that Phoenix comes with a fake mail delivery system. Head out to http://localhost:4000/dev/mailbox and you're going to see your email confirmation is there. Be aware that this is memory only, stopping/restarting the server will make your email preview be lost.

Phoenix auth comes bundled with the following features:

  • Login
  • Registration
  • Token-based sessions with token invalidation
  • Email confirmation
  • Password reset
  • Preview emails without having to setup anything locally
  • User settings page
  • Authenticated routes (we will talk a lot about this later)

Go nuts with it, test it all and enjoy how a single terminal command wrote so much code anyone would hate to do over and over again. When you're done, don't forget to commit it.

Summary

  • mix help COMMAND shows useful help for mix commands you don't know how to use.
  • mix phx.gen.auth setups an entire authentication system for your app with a single command.
  • Phoenix contexts are where you store your business logic.
  • Ecto models handle mapping your database data into Elixir data.
  • Phoenix separates your app into two folders: app_name for business logic and app_name_web for handling user interactions from APIs and HTML interfaces.

Read chapter 3: Adding a points system

Top comments (0)