Jan 30, 2019

Phoenix Authentication with Pow - Part 1

User registration and session management is a very common feature of any non-trivial web application. A custom solution is always an option and is usually pretty easy to implement. This is certainly the case when working with Phoenix, and there are many books and online tutorials that step thru the process of building out a custom authentication solution.

Although not difficult, this can get pretty involved, especially when adding typical features such as registration confirmation, password reset, and session persistence.

Recently I stumbled across Pow… as per the GitHub description, Pow is: “a robust, modular, and extendable authentication and user management solution for Phoenix and Plug-based apps”.

The Pow README section is pretty comprehensive, providing a good description of how to get things up and running. However, I figured a step-by-step tutorial might still be useful, so let’s Power up (ha, ha) and see how it works!

What we’ll build

For the purposes of demonstrating how to use Pow we will be building a very bare bones Phoenix application. We won’t worry about making it look pretty or customizing it in any way, but we will use a generator to create some pages we want to secure with Pow, just to demonstrate how that functionality works.

I’d encourage you to follow along, but if you want to skip directly to the code, it’s available on GitHub at: https://github.com/riebeekn/phx-auth-with-pow.

So let’s get at it!

Creating our app

Our first step is to create our application, we’ll pretend we are building out a basic (super, super, basic) warehouse inventory system, so we’ll call our application warehouse.

Terminal
mix phx.new warehouse

Choose Y when asked to fetch and install dependencies. Now we’ll change into the application directory and create the database.

Terminal
cd warehouse
mix ecto.create

We’ll want to demonstrate how to secure pages when using Pow, so let’s use a generator to create our “inventory” context and our “product” pages. We’ll look to restrict access to these pages, only allowing logged in users to view them.

Terminal
mix phx.gen.html Inventory Product products name:string quantity:integer

As per the mix output, let’s run the migrations…

Terminal
mix ecto.migrate

… and update the routes.

/lib/warehouse_web/router.ex …line 16
scope "/", WarehouseWeb do
  pipe_through :browser

  get "/", PageController, :index
  resources "/products", ProductController
end

Let’s also add a link in the header for navigating to the main products page where we list our products.

/lib/warehouse_web/templates/layout/app.html.eex …line 11
<header>
  <section class="container">
    <nav role="navigation">
      <ul>
        <li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
        <li><%=  link "Products", to: Routes.product_path(@conn, :index) %></li>
      </ul>
      ...
      ...

That is pretty much it as far as our application functionality goes so let’s fire up the application and see what we got.

Terminal
mix phx.server

If we navigate to http://localhost:4000/ we’ll see the standard Phoenix landing page, and the Products link will take us to the main Products page.

So with a very basic application in place, let’s see how we can add user authentication and management to our application via Pow.

Using Pow within our application

There are a number of options and extensions available to us when using Pow but we’ll start with a basic installation, and go from there.

Basic Installation

The first step is to add pow to our list of dependencies.

/mix.exs …line 34
defp deps do
  [
    {:phoenix, "~> 1.4.0"},
    {:phoenix_pubsub, "~> 1.1"},
    {:phoenix_ecto, "~> 4.0"},
    {:ecto_sql, "~> 3.0"},
    {:postgrex, ">= 0.0.0"},
    {:phoenix_html, "~> 2.11"},
    {:phoenix_live_reload, "~> 1.2", only: :dev},
    {:gettext, "~> 0.11"},
    {:jason, "~> 1.0"},
    {:plug_cowboy, "~> 2.0"},
    {:pow, "~> 1.0.0"}
  ]
end

Now we need to grab our dependencies via mix deps.get.

Terminal
mix deps.get

The mix output will indicate that pow has been added to our application.

Next we need to install Pow.

Terminal
mix pow.install

Now we need to deal with a bit of configuration.

Configuration

First update config.ex, adding a new configuration section for Pow. I’ve placed this above the logger configuration.

/config/config.exs
...
...
# Pow configuration
config :warehouse, :pow,
  user: Warehouse.Users.User,
  repo: Warehouse.Repo

# Configures Elixir's Logger
...
...

Next we need to update endpoint.ex.

/lib/warehouse_web/endpoint.ex …line 40
plug Plug.Session,
  store: :cookie,
  key: "_warehouse_key",
  signing_salt: "ZvZ33+3H"

# enable Pow session based authentication
plug Pow.Plug.Session, otp_app: :warehouse

We’ve added a pow specific plug directly after the session plug.

That’s it for configuration, we now need to add some routes.

Route updates

There are some Pow specific routes we need to add, and we also need a specific pipeline for protected resources.

/lib/warehouse_web/routes.ex
defmodule WarehouseWeb.Router do
  use WarehouseWeb, :router
  use Pow.Phoenix.Router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  # BEGIN added for Pow
  pipeline :protected do
    plug Pow.Plug.RequireAuthenticated,
      error_handler: Pow.Phoenix.PlugErrorHandler
  end

  scope "/" do
    pipe_through :browser

    pow_routes()
  end

  scope "/", WarehouseWeb do
    pipe_through [:browser, :protected]

    # Add your protected routes here
  end
  # END added for Pow

  scope "/", WarehouseWeb do
    pipe_through :browser

    get "/", PageController, :index
    resources "/products", ProductController
  end
end

So we’ve added a new use statement for the Pow routes (use Pow.Phoenix.Router); a new pipeline; and two new scopes. We can see that the second scope is where our protected routes should be going.

Running migrations

Finally, when we installed Pow, a new migration file was created, so we also need to run migrations.

Terminal
mix ecto.migrate

Using TablePlus we can view the structure of the users table created for us by Pow.

Looks good!

Before we run our application let’s have a look at our routes.

Terminal
mix phx.routes

Pow has made available a number of new routes for us.

Let’s update our navigation to include links to relevent routes. We also now have access to a current_user variable, so can display appropriate links based on whether a user is currently logged in or not.

We’ll always display the Products link… just to make it easy to test whether it has been properly protected.

/lib/warehouse_web/templates/layout/app.html.eex …line 13
<nav role="navigation">
  <ul>
    <%= if @current_user do %>
      <li>Signed in as: <%= @current_user.email %></li>
      <li><%= link "Sign out", to: Routes.pow_session_path(@conn, :delete), method: :delete %></li>
      <li><%= link "Edit account", to: Routes.pow_registration_path(@conn, :edit) %></li>
    <% else %>
      <li><%= link "Sign in", to: Routes.pow_session_path(@conn, :new) %></li>
      <li><%= link "Sign up", to: Routes.pow_registration_path(@conn, :new) %></li>
    <% end %>
    <li><%=  link "Products", to: Routes.product_path(@conn, :index) %></li>
  </ul>
</nav>

Ok, with all these changes in place let’s start our server back up and see how things are looking.

Checking out the Pow functionality

Terminal
mix phx.server

Our new links are appearing in the header.

Following the sign up link we see the registration page Pow creates for us:

After signing up, we are automatically signed in.

We now have access to pages that are specific to signed in users, such as the “Edit profile / account” page.

If we sign-out we’ll see that despite being signed out we can still access the products page.

Let’s secure the page by updating our routes.

/lib/warehouse_web/router.ex …line 29
scope "/", WarehouseWeb do
  pipe_through [:browser, :protected]

  # Add your protected routes here
  resources "/products", ProductController
end
# END added for Pow

scope "/", WarehouseWeb do
  pipe_through :browser

  get "/", PageController, :index
end

We’ve removed the ‘products’ resources from the lower scope and placed them in the protected scope. Clicking the Products link now re-directs to the sign in page.

Customizing the Pow templates

So this is pretty sweet! We have a good amount of functionality and it’s required very little effort or set-up.

What if we’re not happy with the templates Pow creates however? Before stopping for the day, let’s see how easy it is to customize the Pow templates.

The first step is to generate the templates.

Terminal
mix pow.phoenix.gen.templates

We then need to update config.exs to indicate we want to use the generated templates.

/config/config.exs …line 20
# Pow configuration
config :warehouse, :pow,
  user: Warehouse.Users.User,
  repo: Warehouse.Repo,
  web_module: WarehouseWeb

We’ve added the web_module value, and we can now update the templates in lib/warehouse_web/templates/pow and we’ll see our changes reflected in our application. Changing the configuration requires a server restart, so let’s restart our server.

Terminal
mix phx.server

And then make a few small changes to the Pow templates.

/lib/warehouse_web/templates/pow/session/new.html.eex …line 18
<div>
  <%= submit "Sign in now!" %>
</div>

We’ve updated the text to read Sign in now!, and this get’s reflected in our application.

A pretty trivial change, something that might make a bit more sense is adding a “Close account” link to the edit profile page.

/lib/warehouse_web/templates/pow/registration/edit.html.eex …line 26
<div>
  <%= submit "Update" %>
</div>

<div>
  <%= link "Close account", to: Routes.pow_registration_path(@conn, :delete),
    method: :delete, data: [confirm: "Are you sure?"]
  %>
</div>

And voila, user’s now have a way to close out their account.

If we ever want to revert to the default templates, we can just remove the config.exs web_module entry.

/config/config.exs …line 20
# Pow configuration
config :warehouse, :pow,
  user: Warehouse.Users.User,
  repo: Warehouse.Repo #,
  # web_module: WarehouseWeb

After a server restart, we’re back to our original templates.

I like having that “Close account” available on the Edit page however, so let’s switch the configuration back to include web_module: WarehouseWeb.

/config/config.exs …line 20
# Pow configuration
config :warehouse, :pow,
  user: Warehouse.Users.User,
  repo: Warehouse.Repo,
  web_module: WarehouseWeb

Summary

That’s all for today, the bare bones behaviour provided by Pow is pretty impressive, especially considering how easy it is to get set up. There is more goodness on the horizon however, next time we’ll look at how easy it is to add session persistence, registration confirmations and forgot password functionality.

Thanks for reading and I hope you enjoyed the post!



Comment on this post!