Subscribe to access all episodes. View plans →

#122: Using phx_gen_auth for Phoenix Authentication

Published August 24, 2020

Elixir 1.10

Phoenix 1.5

Phx.Gen.Auth 0.4.0


phx_gen_auth is a new package that makes it incredibly easy to add authentication to Phoenix applications. One note phx_gen_auth requires at least Phoenix 1.5, so before you can use it make sure your application is upgraded to use at least Phoenix 1.5.

Let’s take a look at the app we’ll be adding authentication to. It’s a simple blog. And currently anyone can add posts. Let’s add authentication so that we have to be signed in in order to create or edit posts.

We’ll start by grabbing the package from Hex and adding it to our Mixfile we’ll only want this in our dev environment, and we’ll set runtime to false.

mix.exs

...

defp deps do
[

  ...

  {:phx_gen_auth, "~> 0.4.0", only: [:dev], runtime: false},

  ...

]
end

...

Then let’s go to the command line and install the dependency.

$ mix deps.get
...
New:
  phx_gen_auth 0.4.0
* Getting phx_gen_auth (Hex package)

Now that we’ve installed it let’s clear the screen.. phx_gen_auth works by generating our authentication logic inside of our application. This makes it easy to customize in order to fit the needs of your application.

Now in order to create the files we’ll need to manage authentication, we can use the mix phx.gen.auth generator with an Accounts context module, a User, and users for the database table.

$ mix phx.gen.auth Accounts User users
...

As you can see, this created quite a few files, but let’s take a look at a few to get an idea of what was added. Here’s our user.ex schema module that was created for us.

It has a fields for an email, the password virtual field, hashed_password field, and a confirmed_at field. By default, the generated authentication system wants to send a confirmation email, we’ll go over this more in a few minutes, but when a user confirms their email address, the confirmed_at field is set. If we want to restrict access to our application until a user has confirmed their account, we can use this field to here. Our module is generated with different changeset functions to handle things like registering a user and changing their password. If we need to customize anything related to user validation, we can easily do that too. For example, by default the user password length needs to be between 12 and 80 characters. Let’s go ahead and change the minimum password length to 9.

Now let’s take a look at the generated user_token.ex module. Once a user has authenticated, an associated UserToken is created, stored, and then used to authenticated the user. The schema has fields for the token the context this is used to show the scenario the token was generated in, for example if it was created for a new session or a password reset. The sent_to field stores the email address of where to send something like a confirmation email to confirm the user’s email. Tokens also have a belongs_to relationship with our User schema. There are also some options we can change. For example if we wanted to customize how long a password reset is valid for or how long our session is valid for, those defaults are set here in these module attributes.

Let’s also take a look at the user_auth.ex module. This is a plug that has different functions to handle things like logging in the user, logging out the user, and getting the current user from the session. The generator also added links to the UI for users to sign in or sign up.

We’ll open the app.html.eex template the _user_menu.html partial was created and added to our template. This will have our links to sign in and sign out. We’ll move this partial down so the links show up on the top of each page.

Template path: lib/teacher_web/templates/layout/app.html.eex

<main role="main" class="container">
 <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
 <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
 <%= render "_user_menu.html", assigns %>
 <%= @inner_content %>
</main>

Now we can go back to the command line and if we try to run the migration that was generated, we get an error. We have an unchecked dependency, bcrypt_elixir. This is because phx_gen_auth uses bcrypt_elixir to hash user passwords. And since we didn’t have it included in our list of dependencies it added it for us. So let’s run mix deps.get.

$ mix deps.get
...

Now we can run the migration that was generated.

$ mix ecto.migrate
...

Great, with our database migrated let’s start up the server.

$ mix phx.server
...

And if we open up our browser, we see two new links on our page - one to register an account and one to log in. Since we don’t have an account, let’s create one. And we’re taken to a registration page that was created for us.

If we try to create a user with a password that’s too short, the validation is triggered. Let’s go ahead and use a longer password - and great we’re registered and signed in!

Since we’ve authenticated, our user menu links have changed. Our email is displayed along with a link to a settings page and a link to sign out. Let’s check out the settings page. And here we can update our email or change our password. phx_gen_auth did all this for us - we don’t have to spend extra time implementing this feature.

When we log out our link to create a new blog post is still displayed. Let’s fix that now. In our router.ex phx_gen_auth added the fetch_current_user plug that looks if a user has authenticated and if they have, it puts the user on the connection under the :current_user key.

So we can open our post’s index.html.eex template and update it to only show the link if the user is signed in by the presence of a @current_user.

Template path: lib/teacher_web/templates/post/index.html.eex

<%= if @current_user do %>
<span><%= link "New Post", to: Routes.post_path(@conn, :new) %></span>
<% end %>
...

We also have a link to edit the post on the post’s show page. Let’s update that too.

Template path: lib/teacher_web/templates/post/show.html.eex

...
<%= if @current_user do %>
  <%= link "Edit", to: Routes.post_path(@conn, :edit, @post) %>
<% end %>
...

If we go back to the browser our links to create and edit posts no longer show up on the page, but we can still access those pages by going directly to them.

Back to our our router.ex there is a section of “Authentication routes”, which were created for us. We could extract our post routes from our unauthenticated scope above and put them here, but since we can already see how that works here, let’s include this require_authenticated_user plug in our post_controller.ex instead.

We’ll open the post_controller.ex and import the UserAuth module, but we wont use all the functions, so let’s limit this to only import the require_authenticated_user function with an arity of 2. We want this to plug to execute for all of our actions except for the “index” and the “show”.

lib/teacher_web/controllers/post_controller.ex

...

import TeacherWeb.UserAuth, only: [require_authenticated_user: 2]

plug :require_authenticated_user when action not in [:index, :show]

...

Now when we go back to the browser and try to access those pages, we’re stopped and directed to sign in. That’ great. We now need to be signed in in order to create or edit our blog posts.

Earlier in this episode we talked about how phx_gen_auth wants to send a confirmation email when a user registers. If we look at our logs from when we registered, there’s text for a confirmation email being logged.

Those confirmations are handled via in the user_notifier.ex module and opening that we can see the deliver_confirmation_instructions that’s called to deliver the email. Currently we can see that it’s just being logged here. It’s up to us to implement the email notification using an email library like Bamboo or Swoosh.

For more info on how to configure your application to send emails, see episodes 14 and 15 where we go over using Bamboo.

© 2024 HEXMONSTER LLC