Click here to share this article on LinkedIn »

Authentication is integral part of most web applications. In this article, I will show how to authenticate user based on username/password with Guardian library and Phoenix 1.3. Logged in user information will be saved in session so that controllers can easily check and protect sensitive resources from un-authorized users.

The example project I am going to use in this article is based on Chris McCord’s book — Programming Phoenix — Productive |> Reliable |> Fast. I have written the rumbl project from the book and converted it accordingly to support Phoenix 1.3. Only the authentication part has been modified to use Guardian. Entire project code with tests are available on my github here.

The github documentation for Guardian is pretty good but it may be daunting tasks for a newbie to go through all those and make authentication work. The purpose of the article is to decipher some of the text there and provide working code to easily bootstrap your project’s authentication part.

Setting up dependencies:

Add the below dependencies in mix.exs:

defp deps do

[

{:guardian, "~> 1.0"},

{:comeonin, "~> 4.0"},

{:bcrypt_elixir, "~> 1.0"}

]

end

Here —

i. guardian: is the authentication library

ii. comeonin: is a password hashing library for Elixir

iii. bcrypt_elixir: is password hashing algorithm used by comeonin

Implement Guardian Callbacks

Create below module to setup Guardian callbacks (guardian.ex) —

defmodule Rumbl.Auth.Guardian do

@moduledoc false



use Guardian, otp_app: :rumbl



alias Rumbl.Accounts



def subject_for_token(user, _claims) do

sub = to_string(user.id)

{:ok, sub}

end



def resource_from_claims(claims) do

id = claims["sub"]

user = Accounts.get_user(id)

{:ok, user}

end

end

Here, we want to Authenticate user using User schema under Accounts context. (You can read through the comments in this section on Guardian github to understand better the callbacks).

Configure Guardian:

Add the below in config.exs to configure Guardian —

# Configures Guardian

config :rumbl, Rumbl.Auth.Guardian,

issuer: "rumbl",

secret_key: "HNinpKh9Ne3tr8BpjCpAEh0xzCqTIG3PWsfkR2AtzvUaRIpbs6oIQ9RcmjmGPekJ"

Creating password hash during registration:

With the above setup done, we are ready to provide application specific changes to store hashed password in DB and authenticate users when a login request comes through.

Our registration changeset in user.ex looks as below —

def registration_changeset(%User{} = user, attrs) do

user

|> cast(attrs, [:name, :username, :password])

|> validate_required([:name, :username, :password])

|> validate_length(:username, min: 3, max: 10)

|> validate_length(:password, min: 5, max: 10)

|> unique_constraint(:username)

|> put_password_hash()

end defp put_password_hash(changeset) do

case changeset do

%Ecto.Changeset{valid?: true, changes: %{password: pass}} ->

put_change(changeset, :password_hash, Comeonin.Bcrypt.hashpwsalt(pass))



_ ->

changeset

end

end

Here, during registration, we compute a password has using bcrypt and put it the changeset so that it’s saved in DB when a user is registered successfully.

Authenitcating users/login:

Whenever user tries to login, a sessioncontoller#create action is invoked where authentication code is written —

def create(conn, %{"session" => %{"username" => user, "password" => password}}) do

case Rumbl.Auth.authenticate_user(user, password) do

{:ok, user} ->

conn

|> Rumbl.Auth.login(user)

|> put_flash(:info, "Welcome back!")

|> redirect(to: user_path(conn, :index))



{:error, _reason} ->

conn

|> put_flash(:error, "Invalid username/password combination")

|> render("new.html")

end

end

Here, important lines are displayed in bold. Rumbl.Auth.authenticate_user takes the username and password from params and authenticates the user —

def authenticate_user(username, given_password) do

query = Ecto.Query.from(u in User, where: u.username == ^username)



Repo.one(query)

|> check_password(given_password)

end



defp check_password(nil, _), do: {:error, "Incorrect username or password"}



defp check_password(user, given_password) do

case Bcrypt.checkpw(given_password, user.password_hash) do

true -> {:ok, user}

false -> {:error, "Incorrect username or password"}

end

end

The code is pretty self exlaplanatory. Here, user is retrieved from DB first using username and matched with stored DB hashed password in check_password.

If authentication is succssful, Rumbl.Auth.login(user) is called (auth.ex) —

def login(conn, user) do

conn

|> Guardian.Plug.sign_in(user)

|> assign(:current_user, user)

end

This is basically a plug that stores user info in session and assigns user to current_user.

Logout user:

Logout code is very much straightforward. Whenever, user logs out, sessioncontroller#delete action gets invoked and it logs user out —

def delete(conn, _) do

conn

|> Rumbl.Auth.logout()

|> redirect(to: page_path(conn, :index))

end

Highlighted bold part of the code looks as below (auth/auth.ex) —

def logout(conn) do

conn

|> Guardian.Plug.sign_out()

end

Plug pipeline for protecting routes:

After basic authentication is working, we need to protect the routes from unautheticated users. For that, we need to define the below pipeline (auth_access_pipeline.ex) —

defmodule Rumbl.Auth.AuthAccessPipeline do

@moduledoc false



use Guardian.Plug.Pipeline, otp_app: :rumbl



plug(Guardian.Plug.VerifySession, claims: %{"typ" => "access"})

plug(Guardian.Plug.EnsureAuthenticated)

plug(Guardian.Plug.LoadResource)

end

Here —

i. Guardian.Plug.VerifySession — looks for a token in the session and verifies it

ii. Guardian.Plug.EnsureAuthenticated — makes sure that a token was found and is valid

iii. Guardian.Plug.LoadResource — if a token was found, loads the resource for it

Next, we need to add the pipeline and error handler to our configuration in config.exs —

config :rumbl, Rumbl.Auth.AuthAccessPipeline,

module: Rumbl.Auth.Guardian,

error_handler: Rumbl.Auth.AuthErrorHandler

The error handler is a module that implements an auth_error function. Rumbl.Auth.AuthErrorHandler is defined in auth_error_handler.ex —

defmodule Rumbl.Auth.AuthErrorHandler do

@moduledoc false



import Plug.Conn



def auth_error(conn, {type, _reason}, _opts) do

body = Poison.encode!(%{message: to_string(type)})

send_resp(conn, 401, body)

end

end

Next, we need to modify router.ex to use the above mentioned pipeline to protect the routes under authentication —

pipeline :auth do

plug(Rumbl.Auth.AuthAccessPipeline)

end scope "/", RumblWeb do

pipe_through([:browser, :auth])



resources("/users", UserController, only: [:index, :show])

resources("/videos", VideoController)

resources("/sessions", SessionController, only: [:delete])

get("/watch/:id", WatchController, :show)

end

Getting current_user in controllers:

In controllers, we will often need currently logged in user information. For that I have written the below plug —

def load_current_user(conn, _) do

conn

|> assign(:current_user, Guardian.Plug.current_resource(conn))

end

You can put it on top of any controller where current_user is needed —

import Rumbl.Auth, only: [load_current_user: 2]



plug(:load_current_user when action in [:show, :index])

This should get you started with authentication in Phoenix web app using Guardian. Entire project code with tests are available on my github here.

For more elaborate and in depth future technical posts please follow me here or on twitter.