How to create a user authentication systems with email verification in phoenix framework

Phoenix Framework is built with the powerful and resilient programming language called Elixir Lang. It is gaining momentum by the day thanks to it’s expressive syntax which makes you productive and it’s built upon the shoulders of the Erlang vm called BEAM which makes your code performant.

Here will build a user registration with Email verification and a login systems. Phoenix framework can be used to build normal html 5 crud apps, json api and real time backends. We’ll be building a real time backend using Phoenix channels and an Elixir library Guardian jwt.

You can find installation instructions on the Elixir site and Phoenix framework .

We be using Phoenix 1.3 which is the current version.

Lets create our app called sample with this command:

$ mix phx.new sample --no-html --no-brunch
* creating sample/config/config.exs
* creating sample/config/dev.exs
* creating sample/config/prod.exs
* creating sample/config/prod.secret.exs
* creating sample/config/test.exs
* creating sample/lib/sample/application.ex
* creating sample/lib/sample/web/channels/user_socket.ex
* creating sample/lib/sample/web/views/error_helpers.ex
* creating sample/lib/sample/web/views/error_view.ex
* creating sample/lib/sample/web/endpoint.ex
* creating sample/lib/sample/web/router.ex
* creating sample/lib/sample/web/web.ex
* creating sample/mix.exs
* creating sample/README.md
* creating sample/test/support/channel_case.ex
* creating sample/test/support/conn_case.ex
* creating sample/test/test_helper.exs
* creating sample/test/sample/web/views/error_view_test.exs
* creating sample/lib/sample/web/gettext.ex
* creating sample/priv/gettext/en/LC_MESSAGES/errors.po
* creating sample/priv/gettext/errors.pot
* creating sample/lib/sample/repo.ex
* creating sample/priv/repo/seeds.exs
* creating sample/test/support/data_case.ex
* creating sample/.gitignore
Fetch and install dependencies? [Yn] n
We are almost there! The following steps are missing:
    $ cd sample
$ mix deps.get
Then configure your database in config/dev.exs and run:
    $ mix ecto.create
Start your Phoenix app with:
    $ mix phx.server
You can also run your app inside IEx (Interactive Elixir) as:
    $ iex -S mix phx.server

cd into your sample app and go to the mix.ex file and add these libraries in your deps function : {:guardian, “~> 0.14”} , {:swoosh, “~> 0.8.1”}, {:secure_random, “~> 0.5.1”}, {:hasher, “~> 0.1.0”}. These will provide us with Json web token for user authentication , sending email for the user to verify their email address before they can fully logging into our app, and generating a email verification token.

Lets now setup swoosh to send emails, we’ll be using Mailgun api to signup for a free sandbox account to start sending email with their api.

# In your config/config.exs file
config :sample, Sample.Mailer,
adapter: Swoosh.Adapters.Mailgun,
api_key: "x.x.x"
# In your lib/sample folder create mailer.ex
defmodule Sample.Mailer do
use Swoosh.Mailer, otp_app: :sample
end

defmodule Sample.UserEmail do
import Swoosh.Email


# In your lib/sample folder create user_email.ex
defmodule Sample.UserEmail do
import Swoosh.Email
    def welcome(user) do
new()
|> to({user.name, user.email})
|> from({"your app name", "your-appxxx@gmail.com"})
|> subject("Your sample verification email")
|> html_body("<p>Thanks for signing up with us</p>
<p>Please click the link below to verify your email address</p>
<a href=https://sxxxx.com/v1/api/auth/verify_email/# {user.token}>Verify address</a>"
)
end
end

Lets config Guardian library:

# In your config/config.ex  

config :guardian, Guardian,
allowed_algos: ["HS512"], # optional
verify_module: Guardian.JWT, # optional
issuer: "Sample",
ttl: { 30, :days },
allowed_drift: 2000,
verify_issuer: true, # optional
secret_key: System.get_env("GUARDIAN_SECRET") || "xx...xxxx",
serializer: Sample.Web.GuardianSerializer


# In your lib/sample/web folder create guardian_serializer.ex

defmodule Sample.Web.GuardianSerializer do
@behaviour Guardian.Serializer
  alias Sample.Repo
alias Sample.Account.User
  def for_token(user = %User{}), do: { :ok, "User:#{user.id}" }
def for_token(_), do: { :error, "Unknown resource type" }
  def from_token("User:" <> id), do: { :ok, Repo.get(User, id) }
def from_token(_), do: { :error, "Unknown resource type" }
end

Phoenix framework directory structure now favours creating context to access your data model. So lets create Accounts context with apis to access our User model.

#In your lib/sample create account/account.ex
defmodule Sample.Account do
@moduledoc """
The boundary for the Account system.
"""
  import Ecto.Query, warn: false
alias Sample.Repo
  alias Sample.Account.User
  @doc """
Returns the list of users.
  ## Examples
      iex> list_users()
[%User{}, ...]
  """
def list_users do
Repo.all(from u in "account_users",
order_by: u.inserted_at,
select: %{name: u.name, username: u.username, email_verified: u.email_verified, token: u.token, email: u.email, id: u.id})
end
  @doc """
Gets a single user.
  Raises `Ecto.NoResultsError` if the User does not exist.
  ## Examples
      iex> get_user!(123)
%User{}
      iex> get_user!(456)
** (Ecto.NoResultsError)
  """
def get_user!(id), do: Repo.one(from u in "account_users",
where: u.id == ^String.to_integer(id),
select: %{name: u.name, username: u.username, email_verified: u.email_verified, token: u.token, email: u.email, id: u.id})
  @doc """
Creates a user.
  ## Examples
      iex> create_user(%{field: value})
{:ok, %User{}}
      iex> create_user(%{field: bad_value})
{:error, %Ecto.Changeset{}}
  """
def create_user(attrs \\ %{}) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
end
  @doc """
Updates a user.
  ## Examples
      iex> update_user(user, %{field: new_value})
{:ok, %User{}}
      iex> update_user(user, %{field: bad_value})
{:error, %Ecto.Changeset{}}
  """
def update_user(%User{} = user, attrs) do
user
|> User.changeset(attrs)
|> Repo.update()
end

  @doc """
Deletes a User.
  ## Examples
      iex> delete_user(user)
{:ok, %User{}}
      iex> delete_user(user)
{:error, %Ecto.Changeset{}}
  """
def delete_user(%User{} = user) do
Repo.delete(user)
end
  @doc """
Returns an `%Ecto.Changeset{}` for tracking user changes.
  ## Examples
      iex> change_user(user)
%Ecto.Changeset{source: %User{}}
  """
def change_user(%User{} = user) do
User.changeset(user, %{})
end
  def get_by(username) do
Repo.get_by(User, username: username)
end
  def get_by_token(token) do
Repo.get_by(User, token: token)
end
  def verify_email(user) do
user
|> User.verify_changeset()
|> Repo.update()
end
  def get!(id) do
Repo.get!(User, id)
end
end
#In your lib/sample create  account/user.ex for our user schema
defmodule Sample.Account.User do
use Ecto.Schema
import Ecto.Changeset
alias Sample.Account.User
    schema "account_users" do
field :email, :string
field :name, :string
field :username, :string
field :password, :string, virtual: true
field :password_hash, :string
field :email_verified, :boolean
field :token, :string
        timestamps()
end
    def changeset(%User{} = user, params) do
user
|> cast(params, [:name, :email, :username])
|> validate_required([:name, :username, :email])
|> validate_length(:username, min: 5, max: 20)
|> validate_format(:email, ~r/@/)
|> unique_constraint(:email)
|> put_not_verified()
|> put_token()
end
    defp put_not_verified(changeset) do
case changeset do
%Ecto.Changeset{valid?: true} ->
put_change(changeset, :email_verified, false)
_ ->
changeset
end
end
    defp put_token(changeset) do
case changeset do
%Ecto.Changeset{valid?: true} ->
put_change(changeset, :token, SecureRandom.urlsafe_base64())
_ ->
changeset
end
end
    def registration_changeset(model, params \\ %{}) do
model
|> changeset(params)
|> cast(params, [:password])
|> validate_length(:password, min: 4, max: 200)
|> put_pass_hash()
end
    defp put_pass_hash(changeset) do
case changeset do
%Ecto.Changeset{valid?: true, changes: %{password: password}} ->
put_change(changeset, :password_hash, Hasher.salted_password_hash(password))
_ ->
changeset
end
end
    def verify_changeset(%User{} = user, params \\ %{}) do
user
|> cast(params, [])
|> put_verify_email()
end
    defp put_verify_email(changeset) do
case changeset do
%Ecto.Changeset{valid?: true} ->
put_change(changeset, :email_verified, true)
_ ->
changeset
end
end
end

# Let create our migration from your command line
$ mix ecto.gen.migration create_user

cd into your priv/repo/migrations folder. You’ll see the already created migration file.

defmodule Sample.Repo.Migrations.CreateAccountUser do
use Ecto.Migration
  def change do
create table(:account_users) do
add :name, :string
add :username, :string
add :email, :string
add :token, :string
add :email_verified, :boolean
add :password_hash, :string
        timestamps()
end
create unique_index(:account_users, [:email, :username])
end
end

Now all that’s left is to migrate up our database

$ mix ecto.migrate

Now let’s create our authentication module. cd into lib/sample/web folder and create aan auth.ex file

defmodule Sample.Web.Auth do
import Hasher
alias Sample.Account
    def login_by_username_and_pass(username, given_pass) do
user = Account.get_by(username)
if user do
cond do
user.email_verified && check_password_hash(given_pass, user.password_hash) ->
{:ok, user}
user.email_verified && check_password_hash(given_pass, user.password_hash) == false ->
{:err, :unauthorized}
not user.email_verified ->
{:err, :notverified}
end
else
{:err, :notfound}
end

end

end

Let’s now create our route to verify our email address

defmodule Sample.Web.Router do
use Sample.Web, :router
pipeline :api do
plug :accepts, ["json", "json-api"]
plug JaSerializer.Deserializer
end
pipeline :api_auth do  
plug :accepts, ["json", "json-api"]
plug Guardian.Plug.VerifyHeader, realm: "Bearer"
plug Guardian.Plug.LoadResource
plug JaSerializer.Deserializer
end
  scope "/", Sample.Web do
pipe_through :api # Use the default browser stack
  get "/", PageController, :index
end
scope "/v1/api/auth", Sample.Web do
pipe_through :api
get "/verify_email/:token", AccessTokenController, :verify_email
end
end

Lets create our AccessTokenController.

#In your lib/sample/web/controllers/access_token_controller.ex
defmodule Sample.Web.AccessTokenController do
use Sample.Web, :controller
    alias Sample.Account
alias Sample.Account.User
     action_fallback Sample.Web.FallbackController
    def verify_email(conn, %{"token" => token}) do
user = Account.get_by_token(token)
if user do
with {:ok, %User{} = user} <- Account.verify_email(user) do

conn
|> put_status(200)
|> render("verify_email.json", user: user)

end
else
conn
|> put_status(:not_found)
|> json(%{error: "invalid token"})
end
end
end

Lets create our access token view.

defmodule Sample.Web.AccessTokenView do
use Sample.Web, :view
    def render("verify_email.json", %{user: user}) do
%{
verified: user.email_verified
}
end
end

Lets create an access token channel to handle our user registration and login.

#In lib/sample/web/channels create access_token_channel.ex
defmodule Sample.Web.AccessTokenChannel do
use Sample.Web, :channel
alias Sample.Account
alias Sample.UserEmail
alias Sample.Mailer
  def join("access:lobby", _payload, socket) do
{:ok, socket}
end
  def handle_in("create:user", %{"user" => user_params}, socket) do
case Account.create_user(user_params) do
{:ok, user} ->
UserEmail.welcome(user)|>Mailer.deliver
resp = %{data: %{id: user.id,
name: user.name,
username: user.username,
email: user.email,
token: user.token,
verified: user.email_verified }
}
push socket, "create:user", resp
{:reply, :ok, socket}
{:error, _changeset} ->
{:reply, {:error, %{errors: "Could not create user"}}, socket}
end
end

def handle_in("create:login", %{"user" => %{"username" => username, "password" => password}}, socket) do
case Sample.Web.Auth.login_by_username_and_pass(username, password) do
{:ok, user} ->
{:ok, jwt, _claims} = Guardian.encode_and_sign(user, :access)
resp = %{data: %{access_token: jwt}}
push socket, "create", resp
{:noreply, socket}
        {:err, :notverified} ->
{:reply, {:error, %{errors: "please verify your email address"}}, socket}
{:err, :unauthorized} ->
{:reply, {:error, %{errors: "user password incorrect"}}, socket}
{:err, :notfound} ->
{:reply, {:error, %{errors: "user not found"}}, socket}
end
end
end

#In your lib/sample/web/channels/user_socket.ex file add to the list of channels
channel "access:*", Sample.Web.AccessTokenChannel

This concludes our user signup and login system for a real time backend server. Reach me egaleme@gmail.com or tweet me twitter.com/AlemeGabriel